From 60bb8e6eb6349f97b08aaa588c335c68f128ccc4 Mon Sep 17 00:00:00 2001 From: Stephen Gold Date: Fri, 6 Oct 2023 08:50:55 -0700 Subject: [PATCH] delete carriage-return characters from 229 text files --- .../jme3utilities/minie/wizard/Action.java | 382 +- .../jme3utilities/minie/wizard/AngleMode.java | 92 +- .../jme3utilities/minie/wizard/BonesMode.java | 330 +- .../minie/wizard/BonesScreen.java | 462 +-- .../minie/wizard/FilePathMode.java | 322 +- .../minie/wizard/FilePathScreen.java | 652 ++-- .../jme3utilities/minie/wizard/LinksMode.java | 502 +-- .../minie/wizard/LinksScreen.java | 1326 +++---- .../jme3utilities/minie/wizard/LoadMode.java | 468 +-- .../minie/wizard/LoadScreen.java | 650 ++-- .../jme3utilities/minie/wizard/Model.java | 2810 +++++++------- .../minie/wizard/RomCallable.java | 738 ++-- .../jme3utilities/minie/wizard/TestMode.java | 1302 +++---- .../minie/wizard/TestScreen.java | 904 ++--- .../jme3utilities/minie/wizard/TorsoMode.java | 360 +- .../minie/wizard/TorsoScreen.java | 454 +-- .../Interface/Nifty/screens/wizard/bones.xml | 144 +- .../Nifty/screens/wizard/filePath.xml | 152 +- .../Interface/Nifty/screens/wizard/links.xml | 300 +- .../Interface/Nifty/screens/wizard/load.xml | 258 +- .../Interface/Nifty/screens/wizard/test.xml | 274 +- .../Interface/Nifty/screens/wizard/torso.xml | 136 +- .../main/resources/Textures/icons/license.txt | 40 +- .../jme3test/bullet/TestBetterCharacter.java | 594 +-- .../java/jme3test/bullet/TestBrickWall.java | 408 +- .../jme3test/bullet/TestPhysicsCharacter.java | 424 +-- .../java/jme3test/bullet/TestWalkingChar.java | 920 ++--- .../java/jme3test/games/RollingTheMonkey.java | 822 ++-- .../jme3test/helloworld/HelloPhysics.java | 450 +-- MinieAssets/build.gradle | 394 +- .../minie/test/models/ImportCgms.java | 584 +-- .../minie/test/models/package-info.java | 60 +- .../minie/test/shapes/MakeAnkh.java | 264 +- .../minie/test/shapes/MakeBanana.java | 266 +- .../minie/test/shapes/MakeBarrel.java | 244 +- .../minie/test/shapes/MakeBowlingPin.java | 266 +- .../minie/test/shapes/MakeDuck.java | 290 +- .../minie/test/shapes/MakeGlyphs.java | 488 +-- .../minie/test/shapes/MakeHeart.java | 690 ++-- .../minie/test/shapes/MakeHorseshoe.java | 266 +- .../minie/test/shapes/MakeSword.java | 290 +- .../minie/test/shapes/MakeTeapot.java | 308 +- .../minie/test/shapes/ProgressListener.java | 180 +- .../minie/test/shapes/ShapeUtils.java | 280 +- .../minie/test/shapes/package-info.java | 60 +- .../minie/test/textures/MakeGreenTile.java | 240 +- .../minie/test/textures/MakePlaid.java | 254 +- .../minie/test/textures/MakePoolBalls.java | 534 +-- .../minie/test/textures/package-info.java | 60 +- .../resources/Interface/Fonts/license.txt | 48 +- .../main/resources/Models/Ankh/license.txt | 12 +- .../main/resources/Models/Banana/license.txt | 12 +- .../main/resources/Models/Barrel/license.txt | 12 +- .../resources/Models/BowlingPin/license.txt | 12 +- .../resources/Models/CandyDish/license.txt | 12 +- .../resources/Models/Horseshoe/license.txt | 12 +- .../main/resources/Models/MhGame/license.txt | 42 +- MinieDump/build.gradle | 50 +- .../jme3utilities/minie/test/BuoyDemo.java | 1378 +++---- .../java/jme3utilities/minie/test/Drop.java | 1784 ++++----- .../jme3utilities/minie/test/DropTest.java | 1922 +++++----- .../minie/test/DropTestStatus.java | 1112 +++--- .../jme3utilities/minie/test/ForceDemo.java | 658 ++-- .../jme3utilities/minie/test/JointDemo.java | 988 ++--- .../minie/test/JointElasticity.java | 804 ++-- .../minie/test/JointElasticityStatus.java | 718 ++-- .../minie/test/MultiSphereDemo.java | 186 +- .../minie/test/NewtonsCradle.java | 952 ++--- .../jme3utilities/minie/test/Pachinko.java | 1132 +++--- .../jme3utilities/minie/test/PoolDemo.java | 1414 +++---- .../jme3utilities/minie/test/RopeDemo.java | 2270 +++++------ .../jme3utilities/minie/test/SeJointDemo.java | 1346 +++---- .../jme3utilities/minie/test/SplitDemo.java | 2390 ++++++------ .../minie/test/SplitDemoStatus.java | 716 ++-- .../jme3utilities/minie/test/TargetDemo.java | 2238 +++++------ .../minie/test/TargetDemoStatus.java | 1366 +++---- .../jme3utilities/minie/test/TestDac.java | 2374 ++++++------ .../minie/test/TestGearJoint.java | 796 ++-- .../minie/test/TestMultiBody.java | 800 ++-- .../jme3utilities/minie/test/TestPin.java | 428 +-- .../minie/test/TestSoftBody.java | 2058 +++++----- .../minie/test/TestSoftBodyControl.java | 802 ++-- .../jme3utilities/minie/test/Windlass.java | 1398 +++---- .../minie/test/common/PhysicsDemo.java | 1846 ++++----- .../minie/test/controllers/package-info.java | 60 +- .../minie/test/issue/TestIssue13.java | 426 +-- .../minie/test/issue/TestIssue18Gimpact.java | 456 +-- .../test/issue/TestIssue18Heightfield.java | 442 +-- .../minie/test/issue/TestIssue18Mesh.java | 456 +-- .../minie/test/issue/package-info.java | 60 +- .../minie/test/mesh/TubeTreeMesh.java | 1430 +++---- .../minie/test/mesh/package-info.java | 60 +- .../minie/test/package-info.java | 60 +- .../minie/test/shape/CompoundTestShapes.java | 1960 +++++----- .../minie/test/shape/MinieTestShapes.java | 722 ++-- .../minie/test/shape/ShapeGenerator.java | 1466 +++---- .../minie/test/shape/package-info.java | 60 +- .../minie/test/terrain/MinieTestTerrains.java | 560 +-- .../minie/test/terrain/package-info.java | 60 +- .../minie/test/tunings/Binocular.java | 144 +- .../minie/test/tunings/Biped.java | 100 +- .../minie/test/tunings/ElephantControl.java | 350 +- .../minie/test/tunings/Face.java | 110 +- .../minie/test/tunings/JaimeControl.java | 478 +-- .../minie/test/tunings/MhGameControl.java | 336 +- .../minie/test/tunings/NinjaControl.java | 332 +- .../minie/test/tunings/OtoControl.java | 312 +- .../minie/test/tunings/PuppetControl.java | 342 +- .../minie/test/tunings/SinbadControl.java | 444 +-- .../minie/test/tunings/package-info.java | 62 +- .../main/resources/Models/Puppet/license.txt | 20 +- MinieLibrary/build.gradle | 1050 ++--- MinieLibrary/release-notes-pre10.md | 1940 +++++----- MinieLibrary/release-notes.md | 2490 ++++++------ .../com/jme3/bullet/MultiBodyJointType.java | 128 +- .../java/com/jme3/bullet/RotationOrder.java | 200 +- .../main/java/com/jme3/bullet/SolverType.java | 142 +- .../bullet/animation/CenterHeuristic.java | 216 +- .../jme3/bullet/animation/IKController.java | 420 +- .../bullet/animation/KinematicSubmode.java | 120 +- .../jme3/bullet/animation/MassHeuristic.java | 104 +- .../com/jme3/bullet/animation/RagUtils.java | 1738 ++++----- .../jme3/bullet/animation/ShapeHeuristic.java | 144 +- .../jme3/bullet/animation/package-info.java | 70 +- .../jme3/bullet/collision/package-info.java | 72 +- .../shapes/GImpactCollisionShape.java | 586 +-- .../shapes/HeightfieldCollisionShape.java | 924 ++--- .../collision/shapes/HullCollisionShape.java | 1348 +++---- .../collision/shapes/MeshCollisionShape.java | 808 ++-- .../shapes/infos/BoundingValueHierarchy.java | 414 +- .../collision/shapes/infos/package-info.java | 70 +- .../bullet/collision/shapes/package-info.java | 72 +- .../com/jme3/bullet/control/UseTriangles.java | 118 +- .../com/jme3/bullet/control/package-info.java | 72 +- .../com/jme3/bullet/debug/package-info.java | 70 +- .../java/com/jme3/bullet/joints/JointEnd.java | 102 +- .../jme3/bullet/joints/motors/MotorParam.java | 604 +-- .../bullet/joints/motors/package-info.java | 70 +- .../com/jme3/bullet/joints/package-info.java | 70 +- .../com/jme3/bullet/objects/infos/Aero.java | 148 +- .../jme3/bullet/objects/infos/Cluster.java | 306 +- .../com/jme3/bullet/objects/infos/Sbcp.java | 548 +-- .../bullet/objects/infos/package-info.java | 70 +- .../com/jme3/bullet/objects/package-info.java | 70 +- .../java/com/jme3/bullet/package-info.java | 70 +- .../com/jme3/bullet/util/DebugMeshKey.java | 338 +- .../com/jme3/bullet/util/NativeMeshUtil.java | 100 +- .../com/jme3/bullet/util/package-info.java | 70 +- .../jme3utilities/minie/AppDataFilter.java | 210 +- .../java/jme3utilities/minie/CcdFilter.java | 172 +- .../java/jme3utilities/minie/ClassFilter.java | 166 +- .../jme3utilities/minie/DacUserFilter.java | 224 +- .../java/jme3utilities/minie/DumpFlags.java | 228 +- .../jme3utilities/minie/MinieVersion.java | 128 +- .../java/jme3utilities/minie/MyControlP.java | 836 ++-- .../main/java/jme3utilities/minie/MyPco.java | 280 +- .../java/jme3utilities/minie/MyShape.java | 1090 +++--- .../minie/NegativeAppDataFilter.java | 212 +- .../jme3utilities/minie/PhysicsDescriber.java | 2576 ++++++------- .../jme3utilities/minie/PhysicsDumper.java | 3372 ++++++++--------- .../java/jme3utilities/minie/UserFilter.java | 210 +- .../jme3utilities/minie/package-info.java | 60 +- .../src/main/java/vhacd/package-info.java | 10 +- .../src/main/java/vhacd4/package-info.java | 10 +- .../com/jme3/bullet/util/package-info.java | 70 +- .../minie/test/package-info.java | 60 +- README.md | 918 ++--- TutorialApps/build.gradle | 76 +- .../tutorial/HelloApplyScale.java | 374 +- .../jme3utilities/tutorial/HelloBoneLink.java | 320 +- .../java/jme3utilities/tutorial/HelloCcd.java | 218 +- .../tutorial/HelloCharacter.java | 544 +-- .../tutorial/HelloCharacterControl.java | 532 +-- .../jme3utilities/tutorial/HelloCloth.java | 222 +- .../tutorial/HelloContactResponse.java | 232 +- .../tutorial/HelloCustomDebug.java | 326 +- .../java/jme3utilities/tutorial/HelloDac.java | 206 +- .../jme3utilities/tutorial/HelloDamping.java | 228 +- .../tutorial/HelloDeactivation.java | 284 +- .../tutorial/HelloDebugToPost.java | 260 +- .../jme3utilities/tutorial/HelloDoor.java | 844 ++--- .../tutorial/HelloDoubleEnded.java | 748 ++-- .../jme3utilities/tutorial/HelloJoint.java | 746 ++-- .../tutorial/HelloKinematicRbc.java | 380 +- .../tutorial/HelloKinematics.java | 284 +- .../jme3utilities/tutorial/HelloLimit.java | 760 ++-- .../tutorial/HelloLocalPhysics.java | 386 +- .../tutorial/HelloMadMallet.java | 246 +- .../tutorial/HelloMassDistribution.java | 286 +- .../tutorial/HelloMinkowski.java | 178 +- .../jme3utilities/tutorial/HelloMotor.java | 584 +-- .../jme3utilities/tutorial/HelloNewHinge.java | 568 +-- .../tutorial/HelloNonUniformGravity.java | 306 +- .../java/jme3utilities/tutorial/HelloPin.java | 230 +- .../java/jme3utilities/tutorial/HelloPoi.java | 886 ++--- .../java/jme3utilities/tutorial/HelloRbc.java | 312 +- .../tutorial/HelloRigidBody.java | 204 +- .../jme3utilities/tutorial/HelloServo.java | 624 +-- .../jme3utilities/tutorial/HelloSoftBody.java | 308 +- .../jme3utilities/tutorial/HelloSoftRope.java | 182 +- .../jme3utilities/tutorial/HelloSoftSoft.java | 242 +- .../jme3utilities/tutorial/HelloSpring.java | 782 ++-- .../tutorial/HelloStaticBody.java | 188 +- .../jme3utilities/tutorial/HelloUpdate.java | 312 +- .../jme3utilities/tutorial/HelloVehicle.java | 388 +- .../jme3utilities/tutorial/HelloWalk.java | 698 ++-- .../tutorial/HelloWalkOtoBcc.java | 758 ++-- .../tutorial/HelloWalkOtoCc.java | 752 ++-- .../jme3utilities/tutorial/HelloWind.java | 510 +-- .../jme3utilities/tutorial/package-info.java | 62 +- .../jme3utilities/minie/tuner/Action.java | 330 +- .../minie/tuner/DecompositionTest.java | 1314 +++---- .../minie/tuner/FilePathMode.java | 332 +- .../minie/tuner/FilePathScreen.java | 652 ++-- .../jme3utilities/minie/tuner/LoadMode.java | 398 +- .../jme3utilities/minie/tuner/LoadScreen.java | 500 +-- .../java/jme3utilities/minie/tuner/Model.java | 1698 ++++----- .../minie/tuner/ProgressDialog.java | 244 +- .../jme3utilities/minie/tuner/SaveMode.java | 486 +-- .../jme3utilities/minie/tuner/SaveScreen.java | 450 +-- .../jme3utilities/minie/tuner/TestMode.java | 1854 ++++----- .../jme3utilities/minie/tuner/TestScreen.java | 1250 +++--- .../Nifty/screens/tuner/filePath.xml | 152 +- .../Interface/Nifty/screens/tuner/load.xml | 212 +- .../Interface/Nifty/screens/tuner/save.xml | 604 +-- .../Interface/Nifty/screens/tuner/test.xml | 728 ++-- .../main/resources/Textures/icons/license.txt | 40 +- build.gradle | 126 +- settings.gradle | 62 +- 229 files changed, 61906 insertions(+), 61906 deletions(-) diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/Action.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/Action.java index 7e8228fdc..e6a9149cf 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/Action.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/Action.java @@ -1,191 +1,191 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.renderer.RenderManager; -import java.util.logging.Logger; -import jme3utilities.nifty.bind.BindScreen; -import jme3utilities.nifty.displaysettings.DsScreen; -import jme3utilities.ui.InputMode; - -/** - * Action strings for the DacWizard application. Each String describes a - * user-interface action. By convention, action strings begin with a verb in all - * lowercase and never end with a space (' '). - * - * @author Stephen Gold sgold@sonic.net - */ -final class Action { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(Action.class.getName()); - - final static String browse = "browse"; - final static String dumpAppStates = "dump appStates"; - final static String dumpPhysicsSpace = "dump physicsSpace"; - final static String dumpRenderer = "dump renderer"; - final static String editBindings = "edit bindings"; - final static String editDisplaySettings = "edit displaySettings"; - final static String editLinks = "edit links"; - final static String load = "load"; - final static String morePath = "more path"; - final static String moreRoot = "more root"; - final static String nextAnimation = "next animation"; - final static String nextMassHeuristic = "next massHeuristic"; - final static String nextScreen = "next screen"; - final static String pickLink = "pick link"; - final static String previousAnimation = "previous animation"; - final static String previousScreen = "previous screen"; - final static String rebuild = "rebuild"; - final static String save = "save"; - final static String saveJ3o = "saveJ3o"; - final static String selectCenterHeuristic = "select centerHeuristic"; - final static String selectRotationOrder = "select rotationOrder"; - final static String selectShapeHeuristic = "select shapeHeuristic"; - final static String setAnimationTime = "set animationTime"; - final static String setMargin = "set margin"; - final static String setMassParameter = "set massParameter"; - final static String setShapeScale = "set shapeScale"; - final static String toggleAngleMode = "toggle angleMode"; - final static String toggleAxes = "toggle axes"; - final static String toggleMesh = "toggle mesh"; - final static String togglePhysicsDebug = "toggle physicsDebug"; - final static String toggleRagdoll = "toggle ragdoll"; - final static String toggleSkeleton = "toggle skeleton"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private Action() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Process an ongoing action from the GUI or keyboard that wasn't handled by - * the active InputMode. - * - * @param actionString textual description of the action (not null) - * @return true if the action has been handled, otherwise false - */ - static boolean processOngoing(String actionString) { - boolean handled = true; - - Model model = DacWizard.getModel(); - switch (actionString) { - case dumpAppStates: - dumpAppStates(); - break; - - case dumpPhysicsSpace: - dumpPhysicsSpace(); - break; - - case dumpRenderer: - dumpRenderer(); - break; - - case editBindings: - editBindings(); - break; - - case editDisplaySettings: - editDisplaySettings(); - break; - - case nextAnimation: - model.nextAnimation(); - break; - - case previousAnimation: - model.previousAnimation(); - break; - - default: - handled = false; - } - - return handled; - } - // ************************************************************************* - // private methods - - /** - * Process a "dump appStates" action. - */ - private static void dumpAppStates() { - DacWizard app = DacWizard.getApplication(); - AppStateManager stateManager = app.getStateManager(); - DacWizard.dumper.dump(stateManager); - } - - /** - * Process a "dump physicsSpace" action. - */ - private static void dumpPhysicsSpace() { - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - DacWizard.dumper.dump(physicsSpace); - } - - /** - * Process a "dump renderer" action. - */ - private static void dumpRenderer() { - DacWizard app = DacWizard.getApplication(); - RenderManager renderManager = app.getRenderManager(); - DacWizard.dumper.dump(renderManager); - } - - /** - * Process an "edit bindings" action. - */ - private static void editBindings() { - BindScreen bindScreen = DacWizard.findAppState(BindScreen.class); - InputMode active = InputMode.getActiveMode(); - bindScreen.activate(active); - } - - /** - * Process an "edit displaySettings" action. - */ - private static void editDisplaySettings() { - DsScreen dss = DacWizard.findAppState(DsScreen.class); - dss.activate(); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.renderer.RenderManager; +import java.util.logging.Logger; +import jme3utilities.nifty.bind.BindScreen; +import jme3utilities.nifty.displaysettings.DsScreen; +import jme3utilities.ui.InputMode; + +/** + * Action strings for the DacWizard application. Each String describes a + * user-interface action. By convention, action strings begin with a verb in all + * lowercase and never end with a space (' '). + * + * @author Stephen Gold sgold@sonic.net + */ +final class Action { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(Action.class.getName()); + + final static String browse = "browse"; + final static String dumpAppStates = "dump appStates"; + final static String dumpPhysicsSpace = "dump physicsSpace"; + final static String dumpRenderer = "dump renderer"; + final static String editBindings = "edit bindings"; + final static String editDisplaySettings = "edit displaySettings"; + final static String editLinks = "edit links"; + final static String load = "load"; + final static String morePath = "more path"; + final static String moreRoot = "more root"; + final static String nextAnimation = "next animation"; + final static String nextMassHeuristic = "next massHeuristic"; + final static String nextScreen = "next screen"; + final static String pickLink = "pick link"; + final static String previousAnimation = "previous animation"; + final static String previousScreen = "previous screen"; + final static String rebuild = "rebuild"; + final static String save = "save"; + final static String saveJ3o = "saveJ3o"; + final static String selectCenterHeuristic = "select centerHeuristic"; + final static String selectRotationOrder = "select rotationOrder"; + final static String selectShapeHeuristic = "select shapeHeuristic"; + final static String setAnimationTime = "set animationTime"; + final static String setMargin = "set margin"; + final static String setMassParameter = "set massParameter"; + final static String setShapeScale = "set shapeScale"; + final static String toggleAngleMode = "toggle angleMode"; + final static String toggleAxes = "toggle axes"; + final static String toggleMesh = "toggle mesh"; + final static String togglePhysicsDebug = "toggle physicsDebug"; + final static String toggleRagdoll = "toggle ragdoll"; + final static String toggleSkeleton = "toggle skeleton"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private Action() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Process an ongoing action from the GUI or keyboard that wasn't handled by + * the active InputMode. + * + * @param actionString textual description of the action (not null) + * @return true if the action has been handled, otherwise false + */ + static boolean processOngoing(String actionString) { + boolean handled = true; + + Model model = DacWizard.getModel(); + switch (actionString) { + case dumpAppStates: + dumpAppStates(); + break; + + case dumpPhysicsSpace: + dumpPhysicsSpace(); + break; + + case dumpRenderer: + dumpRenderer(); + break; + + case editBindings: + editBindings(); + break; + + case editDisplaySettings: + editDisplaySettings(); + break; + + case nextAnimation: + model.nextAnimation(); + break; + + case previousAnimation: + model.previousAnimation(); + break; + + default: + handled = false; + } + + return handled; + } + // ************************************************************************* + // private methods + + /** + * Process a "dump appStates" action. + */ + private static void dumpAppStates() { + DacWizard app = DacWizard.getApplication(); + AppStateManager stateManager = app.getStateManager(); + DacWizard.dumper.dump(stateManager); + } + + /** + * Process a "dump physicsSpace" action. + */ + private static void dumpPhysicsSpace() { + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + DacWizard.dumper.dump(physicsSpace); + } + + /** + * Process a "dump renderer" action. + */ + private static void dumpRenderer() { + DacWizard app = DacWizard.getApplication(); + RenderManager renderManager = app.getRenderManager(); + DacWizard.dumper.dump(renderManager); + } + + /** + * Process an "edit bindings" action. + */ + private static void editBindings() { + BindScreen bindScreen = DacWizard.findAppState(BindScreen.class); + InputMode active = InputMode.getActiveMode(); + bindScreen.activate(active); + } + + /** + * Process an "edit displaySettings" action. + */ + private static void editDisplaySettings() { + DsScreen dss = DacWizard.findAppState(DsScreen.class); + dss.activate(); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/AngleMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/AngleMode.java index 6533c9f35..97099d44f 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/AngleMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/AngleMode.java @@ -1,46 +1,46 @@ -/* - Copyright (c) 2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -/** - * Enumerate modes for angle display/input. - * - * @author Stephen Gold sgold@sonic.net - */ -enum AngleMode { - // ************************************************************************* - // values - - /** - * angles in degrees - */ - Degrees, - /** - * angles in radians - */ - Radians -} +/* + Copyright (c) 2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +/** + * Enumerate modes for angle display/input. + * + * @author Stephen Gold sgold@sonic.net + */ +enum AngleMode { + // ************************************************************************* + // values + + /** + * angles in degrees + */ + Degrees, + /** + * angles in radians + */ + Radians +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesMode.java index 87815b056..6e4836ad8 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesMode.java @@ -1,165 +1,165 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "bones" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class BonesMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(BonesMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized input mode. - */ - BonesMode() { - super("bones"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.previousScreen, KeyInput.KEY_PGUP); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - bind(Action.nextScreen, KeyInput.KEY_PGDN); - - bind(Action.previousScreen, KeyInput.KEY_B); - bind(Action.nextScreen, KeyInput.KEY_N); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Set the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - switch (actionString) { - case Action.nextScreen: - nextScreen(); - handled = true; - break; - - case Action.previousScreen: - previousScreen(); - handled = true; - break; - - default: - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Proceed to the "torso" screen if possible. - */ - private void nextScreen() { - String feedback = BonesScreen.feedback(); - if (feedback.isEmpty()) { - setEnabled(false); - InputMode load = InputMode.findMode("torso"); - load.setEnabled(true); - } - } - - /** - * Go back to the "load" screen. - */ - private void previousScreen() { - setEnabled(false); - InputMode load = InputMode.findMode("load"); - load.setEnabled(true); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "bones" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class BonesMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(BonesMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized input mode. + */ + BonesMode() { + super("bones"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.previousScreen, KeyInput.KEY_PGUP); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + bind(Action.nextScreen, KeyInput.KEY_PGDN); + + bind(Action.previousScreen, KeyInput.KEY_B); + bind(Action.nextScreen, KeyInput.KEY_N); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Set the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + switch (actionString) { + case Action.nextScreen: + nextScreen(); + handled = true; + break; + + case Action.previousScreen: + previousScreen(); + handled = true; + break; + + default: + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Proceed to the "torso" screen if possible. + */ + private void nextScreen() { + String feedback = BonesScreen.feedback(); + if (feedback.isEmpty()) { + setEnabled(false); + InputMode load = InputMode.findMode("torso"); + load.setEnabled(true); + } + } + + /** + * Go back to the "load" screen. + */ + private void previousScreen() { + setEnabled(false); + InputMode load = InputMode.findMode("load"); + load.setEnabled(true); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesScreen.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesScreen.java index 62106d340..5bcfa058d 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesScreen.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/BonesScreen.java @@ -1,231 +1,231 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.animation.DacConfiguration; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.controls.TreeBox; -import de.lessvoid.nifty.controls.TreeItem; -import de.lessvoid.nifty.elements.Element; -import java.util.BitSet; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.InitialState; -import jme3utilities.MyString; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "bones" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class BonesScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(BonesScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of the GUI button to proceed to the "torso" screen - */ - private Element nextElement; - /** - * TreeBox to display all bones in the skeleton - */ - private TreeBox treeBox; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - BonesScreen() { - super("bones", "Interface/Nifty/screens/wizard/bones.xml", - InitialState.Disabled); - } - // ************************************************************************* - // new methods exposed - - /** - * Determine user feedback (if any) regarding the "next screen" action. - * - * @return "" if ready to proceed, otherwise an explanatory message - */ - static String feedback() { - Model model = DacWizard.getModel(); - int numBones = model.countBones(); - - String result = ""; - if (model.listLinkedBones().length == 0) { - result = "No bones are linked."; - } else if (model.countVertices(DacConfiguration.torsoName) == 0) { - result = "No mesh vertices for the torso."; - } else { - for (int i = 0; i < numBones; ++i) { - if (model.isBoneLinked(i)) { - String name = model.boneName(i); - if (model.countVertices(name) == 0) { - result = "No mesh vertices for " + MyString.quote(name); - } - } - } - } - - return result; - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("bones"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time the screen shuts down. - */ - @Override - public void onEndScreen() { - treeBox.clear(); - super.onEndScreen(); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - @SuppressWarnings("unchecked") - public void onStartScreen() { - super.onStartScreen(); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - - this.treeBox = getScreen().findNiftyControl("skeleton", TreeBox.class); - if (treeBox == null) { - throw new RuntimeException("missing GUI control: skeleton"); - } - - TreeItem rootItem = new TreeItem<>(); - rootItem.setExpanded(true); - Model model = DacWizard.getModel(); - int numBones = model.countBones(); - - // Create an item for each bone in the model's skeleton. - TreeItem[] boneItems = new TreeItem[numBones]; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - BoneValue value = new BoneValue(boneIndex); - boneItems[boneIndex] = new TreeItem<>(value); - boneItems[boneIndex].setExpanded(true); - } - - // Parent each item. - for (int childIndex = 0; childIndex < numBones; ++childIndex) { - TreeItem childItem = boneItems[childIndex]; - int parentIndex = model.parentIndex(childIndex); - if (parentIndex == -1) { - rootItem.addTreeItem(childItem); - } else { - boneItems[parentIndex].addTreeItem(childItem); - } - } - treeBox.setTree(rootItem); - - // Pre-select items. - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - TreeItem item = boneItems[boneIndex]; - if (model.isBoneLinked(boneIndex)) { - treeBox.selectItem(item); - } - } - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - List> selectedBones = treeBox.getSelection(); - int numSelected = selectedBones.size(); - - Model model = DacWizard.getModel(); - int numBones = model.countBones(); - - String numText = String.format("Selected %d of %d bone%s", - numSelected, numBones, numBones == 1 ? "" : "s"); - setStatusText("numSelected", numText); - - BitSet linkedBones = new BitSet(numBones); - for (TreeItem treeItem : selectedBones) { - BoneValue value = treeItem.getValue(); - int boneIndex = value.boneIndex(); - linkedBones.set(boneIndex); - } - model.setLinkedBones(linkedBones); - - String feedback = feedback(); - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.animation.DacConfiguration; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.controls.TreeBox; +import de.lessvoid.nifty.controls.TreeItem; +import de.lessvoid.nifty.elements.Element; +import java.util.BitSet; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.InitialState; +import jme3utilities.MyString; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "bones" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class BonesScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(BonesScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of the GUI button to proceed to the "torso" screen + */ + private Element nextElement; + /** + * TreeBox to display all bones in the skeleton + */ + private TreeBox treeBox; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + BonesScreen() { + super("bones", "Interface/Nifty/screens/wizard/bones.xml", + InitialState.Disabled); + } + // ************************************************************************* + // new methods exposed + + /** + * Determine user feedback (if any) regarding the "next screen" action. + * + * @return "" if ready to proceed, otherwise an explanatory message + */ + static String feedback() { + Model model = DacWizard.getModel(); + int numBones = model.countBones(); + + String result = ""; + if (model.listLinkedBones().length == 0) { + result = "No bones are linked."; + } else if (model.countVertices(DacConfiguration.torsoName) == 0) { + result = "No mesh vertices for the torso."; + } else { + for (int i = 0; i < numBones; ++i) { + if (model.isBoneLinked(i)) { + String name = model.boneName(i); + if (model.countVertices(name) == 0) { + result = "No mesh vertices for " + MyString.quote(name); + } + } + } + } + + return result; + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("bones"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time the screen shuts down. + */ + @Override + public void onEndScreen() { + treeBox.clear(); + super.onEndScreen(); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + @SuppressWarnings("unchecked") + public void onStartScreen() { + super.onStartScreen(); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + + this.treeBox = getScreen().findNiftyControl("skeleton", TreeBox.class); + if (treeBox == null) { + throw new RuntimeException("missing GUI control: skeleton"); + } + + TreeItem rootItem = new TreeItem<>(); + rootItem.setExpanded(true); + Model model = DacWizard.getModel(); + int numBones = model.countBones(); + + // Create an item for each bone in the model's skeleton. + TreeItem[] boneItems = new TreeItem[numBones]; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + BoneValue value = new BoneValue(boneIndex); + boneItems[boneIndex] = new TreeItem<>(value); + boneItems[boneIndex].setExpanded(true); + } + + // Parent each item. + for (int childIndex = 0; childIndex < numBones; ++childIndex) { + TreeItem childItem = boneItems[childIndex]; + int parentIndex = model.parentIndex(childIndex); + if (parentIndex == -1) { + rootItem.addTreeItem(childItem); + } else { + boneItems[parentIndex].addTreeItem(childItem); + } + } + treeBox.setTree(rootItem); + + // Pre-select items. + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + TreeItem item = boneItems[boneIndex]; + if (model.isBoneLinked(boneIndex)) { + treeBox.selectItem(item); + } + } + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + List> selectedBones = treeBox.getSelection(); + int numSelected = selectedBones.size(); + + Model model = DacWizard.getModel(); + int numBones = model.countBones(); + + String numText = String.format("Selected %d of %d bone%s", + numSelected, numBones, numBones == 1 ? "" : "s"); + setStatusText("numSelected", numText); + + BitSet linkedBones = new BitSet(numBones); + for (TreeItem treeItem : selectedBones) { + BoneValue value = treeItem.getValue(); + int boneIndex = value.boneIndex(); + linkedBones.set(boneIndex); + } + model.setLinkedBones(linkedBones); + + String feedback = feedback(); + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathMode.java index ba5fa8b3b..517eb3493 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathMode.java @@ -1,161 +1,161 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "filePath" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class FilePathMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(FilePathMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - /** - * action-string prefix to alter the filesystem path prefix - */ - final private static String setPathPrefix = "set pathPrefix "; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized input mode. - */ - FilePathMode() { - super("filePath"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - bind(Action.nextScreen, KeyInput.KEY_PGDN); - - bind(Action.nextScreen, KeyInput.KEY_N); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - FilePathScreen screen - = DacWizard.findAppState(FilePathScreen.class); - - if (Action.browse.equals(actionString)) { - screen.browse(); - handled = true; - - } else if (actionString.startsWith(setPathPrefix)) { - String pathPrefix - = MyString.remainder(actionString, setPathPrefix); - screen.setPathPrefix(pathPrefix); - handled = true; - - } else if (Action.nextScreen.equals(actionString)) { - nextScreen(); - handled = true; - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Proceed to the "load" screen if possible. - */ - private void nextScreen() { - String feedback = FilePathScreen.feedback(); - if (feedback.isEmpty()) { - setEnabled(false); - InputMode load = InputMode.findMode("load"); - load.setEnabled(true); - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "filePath" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class FilePathMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(FilePathMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + /** + * action-string prefix to alter the filesystem path prefix + */ + final private static String setPathPrefix = "set pathPrefix "; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized input mode. + */ + FilePathMode() { + super("filePath"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + bind(Action.nextScreen, KeyInput.KEY_PGDN); + + bind(Action.nextScreen, KeyInput.KEY_N); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + FilePathScreen screen + = DacWizard.findAppState(FilePathScreen.class); + + if (Action.browse.equals(actionString)) { + screen.browse(); + handled = true; + + } else if (actionString.startsWith(setPathPrefix)) { + String pathPrefix + = MyString.remainder(actionString, setPathPrefix); + screen.setPathPrefix(pathPrefix); + handled = true; + + } else if (Action.nextScreen.equals(actionString)) { + nextScreen(); + handled = true; + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Proceed to the "load" screen if possible. + */ + private void nextScreen() { + String feedback = FilePathScreen.feedback(); + if (feedback.isEmpty()) { + setEnabled(false); + InputMode load = InputMode.findMode("load"); + load.setEnabled(true); + } + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathScreen.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathScreen.java index cee9442e2..e38ef13df 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathScreen.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/FilePathScreen.java @@ -1,326 +1,326 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.elements.Element; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InitialState; -import jme3utilities.MyString; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.nifty.PopupMenuBuilder; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "filePath" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class FilePathScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger - = Logger.getLogger(FilePathScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of GUI button to proceed to the "load" screen - */ - private Element nextElement; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - FilePathScreen() { - super("filePath", "Interface/Nifty/screens/wizard/filePath.xml", - InitialState.Disabled); - setSubmenuWarp(0.5f, 0.5f); - } - // ************************************************************************* - // new methods exposed - - /** - * Handle a "browse" action to begin browsing the file system. - */ - void browse() { - Map fileMap = Heart.driveMap(); - - // Add the current working directory to the file map. - String workPath = System.getProperty("user.dir"); - File work = new File(workPath); - if (work.isDirectory()) { - String absolutePath = Heart.fixPath(workPath); - fileMap.put(absolutePath, work); - } - - // Add the user's home directory to the file map. - String homePath = System.getProperty("user.home"); - File home = new File(homePath); - if (home.isDirectory()) { - String absolutePath = Heart.fixPath(homePath); - fileMap.put(absolutePath, home); - } - /* - * If a filesystem path is selected, add its parent directory - * to the file map. - */ - Model model = DacWizard.getModel(); - String filePath = model.filePath(); - if (!filePath.isEmpty()) { - File file = new File(filePath); - File parent = file.getParentFile(); - String parentPath = parent.getPath(); - String absolutePath = Heart.fixPath(parentPath); - fileMap.put(absolutePath, parent); - } - - // Build and show a popup menu. - String actionPrefix = "set pathPrefix "; - PopupMenuBuilder builder = buildFileMenu(fileMap); - showPopupMenu(actionPrefix, builder); - } - - /** - * Determine user feedback (if any) regarding the "next screen" action. - * - * @return "" if ready to proceed, otherwise an explanatory message - */ - static String feedback() { - Model model = DacWizard.getModel(); - String filePath = model.filePath(); - - String result = ""; - if (!filePath.contains("/")) { - result = "No model is selected yet."; - } - - return result; - } - - /** - * Handle a "set pathPrefix" action. - * - * @param pathPrefix the user-selected filesystem-path prefix (not null, not - * empty) - */ - void setPathPrefix(String pathPrefix) { - assert pathPrefix != null; - - String absPathPrefix = Heart.fixPath(pathPrefix); - File file = new File(absPathPrefix); - - boolean isDirectory = file.isDirectory(); - if (!isDirectory && file.canRead()) { // complete path to readable file - Model model = DacWizard.getModel(); - model.setFilePath(absPathPrefix); - - } else { - Map fileMap; - String actionPrefix; - if (isDirectory) { - fileMap = directoryMap(absPathPrefix, ""); - actionPrefix = "set pathPrefix " + absPathPrefix; - - } else { // an incomplete path - File parent = file.getParentFile(); - String parentPath = parent.getPath(); - parentPath = Heart.fixPath(parentPath); - - String name = file.getName(); - fileMap = directoryMap(parentPath, name); - actionPrefix = "set pathPrefix " + parentPath; - } - if (!actionPrefix.endsWith("/")) { - actionPrefix += "/"; - } - - // Build and show a popup menu. - PopupMenuBuilder builder = buildFileMenu(fileMap); - showPopupMenu(actionPrefix, builder); - } - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("filePath"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - Model model = DacWizard.getModel(); - String filePath = model.filePath(); - setStatusText("filePath", " " + filePath); - - String feedback = feedback(); - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - } - // ************************************************************************* - // private methods - - /** - * Build a file-selection popup menu based on the specified file map. - * - * @param fileMap the map of files to include (not null) - * @return a new instance (not null) - */ - private PopupMenuBuilder buildFileMenu(Map fileMap) { - assert fileMap != null; - - // Generate a list of file names (and prefixes) to display in the menu. - Set nameSet = fileMap.keySet(); - assert !nameSet.contains("."); - List nameList = new ArrayList<>(nameSet); - - // Reduce the list as necessary to fit on the screen. - int height = cam.getHeight(); - int maxMenuItems = height / 26; - MyString.reduce(nameList, maxMenuItems); - - // Sort the list and build the menu. - Collections.sort(nameList); - PopupMenuBuilder result = new PopupMenuBuilder(); - for (String name : nameList) { - if (fileMap.containsKey(name)) { - File file = fileMap.get(name); - if (file.isDirectory()) { - result.add(name, "Textures/icons/folder.png"); - } else if (name.endsWith(".j3o")) { - result.add(name, "Textures/icons/jme.png"); - } else if (name.endsWith(".glb")) { - result.add(name, "Textures/icons/jme.png"); - } else if (name.endsWith(".gltf")) { - result.add(name, "Textures/icons/jme.png"); - } - } else { // prefix - result.add(name, "Textures/icons/ellipsis.png"); - } - } - - return result; - } - - /** - * Build a map of files, in the specified directory, whose names have the - * specified prefix. - * - * @param dirPath the filesystem path to the directory (not null) - * @param namePrefix required name prefix (not null) - * @return a new instance (not null) - */ - private static Map directoryMap( - String dirPath, String namePrefix) { - assert dirPath != null; - assert namePrefix != null; - - Map fileMap = new TreeMap<>(); - /* - * Initialize the map with subdirectories and readable files. - * Exclude names that start with ".". - */ - File directory = new File(dirPath); - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory() || file.canRead()) { - String name = file.getName(); - if (name.startsWith(namePrefix) && !name.startsWith(".")) { - fileMap.put(name, file); - } - } - } - } - - // Add ".." if a parent directory exists. - File parent = directory.getParentFile(); - if (parent != null) { - if ("..".startsWith(namePrefix)) { - fileMap.put("..", parent); - } - } - - return fileMap; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.elements.Element; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InitialState; +import jme3utilities.MyString; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.nifty.PopupMenuBuilder; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "filePath" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class FilePathScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger + = Logger.getLogger(FilePathScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of GUI button to proceed to the "load" screen + */ + private Element nextElement; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + FilePathScreen() { + super("filePath", "Interface/Nifty/screens/wizard/filePath.xml", + InitialState.Disabled); + setSubmenuWarp(0.5f, 0.5f); + } + // ************************************************************************* + // new methods exposed + + /** + * Handle a "browse" action to begin browsing the file system. + */ + void browse() { + Map fileMap = Heart.driveMap(); + + // Add the current working directory to the file map. + String workPath = System.getProperty("user.dir"); + File work = new File(workPath); + if (work.isDirectory()) { + String absolutePath = Heart.fixPath(workPath); + fileMap.put(absolutePath, work); + } + + // Add the user's home directory to the file map. + String homePath = System.getProperty("user.home"); + File home = new File(homePath); + if (home.isDirectory()) { + String absolutePath = Heart.fixPath(homePath); + fileMap.put(absolutePath, home); + } + /* + * If a filesystem path is selected, add its parent directory + * to the file map. + */ + Model model = DacWizard.getModel(); + String filePath = model.filePath(); + if (!filePath.isEmpty()) { + File file = new File(filePath); + File parent = file.getParentFile(); + String parentPath = parent.getPath(); + String absolutePath = Heart.fixPath(parentPath); + fileMap.put(absolutePath, parent); + } + + // Build and show a popup menu. + String actionPrefix = "set pathPrefix "; + PopupMenuBuilder builder = buildFileMenu(fileMap); + showPopupMenu(actionPrefix, builder); + } + + /** + * Determine user feedback (if any) regarding the "next screen" action. + * + * @return "" if ready to proceed, otherwise an explanatory message + */ + static String feedback() { + Model model = DacWizard.getModel(); + String filePath = model.filePath(); + + String result = ""; + if (!filePath.contains("/")) { + result = "No model is selected yet."; + } + + return result; + } + + /** + * Handle a "set pathPrefix" action. + * + * @param pathPrefix the user-selected filesystem-path prefix (not null, not + * empty) + */ + void setPathPrefix(String pathPrefix) { + assert pathPrefix != null; + + String absPathPrefix = Heart.fixPath(pathPrefix); + File file = new File(absPathPrefix); + + boolean isDirectory = file.isDirectory(); + if (!isDirectory && file.canRead()) { // complete path to readable file + Model model = DacWizard.getModel(); + model.setFilePath(absPathPrefix); + + } else { + Map fileMap; + String actionPrefix; + if (isDirectory) { + fileMap = directoryMap(absPathPrefix, ""); + actionPrefix = "set pathPrefix " + absPathPrefix; + + } else { // an incomplete path + File parent = file.getParentFile(); + String parentPath = parent.getPath(); + parentPath = Heart.fixPath(parentPath); + + String name = file.getName(); + fileMap = directoryMap(parentPath, name); + actionPrefix = "set pathPrefix " + parentPath; + } + if (!actionPrefix.endsWith("/")) { + actionPrefix += "/"; + } + + // Build and show a popup menu. + PopupMenuBuilder builder = buildFileMenu(fileMap); + showPopupMenu(actionPrefix, builder); + } + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("filePath"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + Model model = DacWizard.getModel(); + String filePath = model.filePath(); + setStatusText("filePath", " " + filePath); + + String feedback = feedback(); + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + } + // ************************************************************************* + // private methods + + /** + * Build a file-selection popup menu based on the specified file map. + * + * @param fileMap the map of files to include (not null) + * @return a new instance (not null) + */ + private PopupMenuBuilder buildFileMenu(Map fileMap) { + assert fileMap != null; + + // Generate a list of file names (and prefixes) to display in the menu. + Set nameSet = fileMap.keySet(); + assert !nameSet.contains("."); + List nameList = new ArrayList<>(nameSet); + + // Reduce the list as necessary to fit on the screen. + int height = cam.getHeight(); + int maxMenuItems = height / 26; + MyString.reduce(nameList, maxMenuItems); + + // Sort the list and build the menu. + Collections.sort(nameList); + PopupMenuBuilder result = new PopupMenuBuilder(); + for (String name : nameList) { + if (fileMap.containsKey(name)) { + File file = fileMap.get(name); + if (file.isDirectory()) { + result.add(name, "Textures/icons/folder.png"); + } else if (name.endsWith(".j3o")) { + result.add(name, "Textures/icons/jme.png"); + } else if (name.endsWith(".glb")) { + result.add(name, "Textures/icons/jme.png"); + } else if (name.endsWith(".gltf")) { + result.add(name, "Textures/icons/jme.png"); + } + } else { // prefix + result.add(name, "Textures/icons/ellipsis.png"); + } + } + + return result; + } + + /** + * Build a map of files, in the specified directory, whose names have the + * specified prefix. + * + * @param dirPath the filesystem path to the directory (not null) + * @param namePrefix required name prefix (not null) + * @return a new instance (not null) + */ + private static Map directoryMap( + String dirPath, String namePrefix) { + assert dirPath != null; + assert namePrefix != null; + + Map fileMap = new TreeMap<>(); + /* + * Initialize the map with subdirectories and readable files. + * Exclude names that start with ".". + */ + File directory = new File(dirPath); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory() || file.canRead()) { + String name = file.getName(); + if (name.startsWith(namePrefix) && !name.startsWith(".")) { + fileMap.put(name, file); + } + } + } + } + + // Add ".." if a parent directory exists. + File parent = directory.getParentFile(); + if (parent != null) { + if ("..".startsWith(namePrefix)) { + fileMap.put("..", parent); + } + } + + return fileMap; + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksMode.java index bd2f49ebb..b2d4deadc 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksMode.java @@ -1,251 +1,251 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.KeyInput; -import com.jme3.math.Vector3f; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyVector3f; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "links" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class LinksMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(LinksMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized input mode. - */ - LinksMode() { - super("links"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.previousScreen, KeyInput.KEY_PGUP); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - bind(Action.nextScreen, KeyInput.KEY_PGDN); - - bind(Action.previousScreen, KeyInput.KEY_B); - bind(Action.nextScreen, KeyInput.KEY_N); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Set the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = true; - if (ongoing) { - LinksScreen screen = DacWizard.findAppState(LinksScreen.class); - switch (actionString) { - case Action.nextMassHeuristic: - screen.nextMassHeuristic(); - break; - - case Action.nextScreen: - nextScreen(); - break; - - case Action.previousScreen: - previousScreen(); - break; - - case Action.selectCenterHeuristic: - screen.selectCenterHeuristic(); - break; - - case Action.selectRotationOrder: - screen.selectRotationOrder(); - break; - - case Action.selectShapeHeuristic: - screen.selectShapeHeuristic(); - break; - - case Action.setMassParameter: - screen.setMassParameter(); - break; - - case Action.setShapeScale: - screen.setShapeScale(); - break; - - case Action.toggleAngleMode: - DacWizard.getModel().toggleAngleMode(); - break; - - default: - handled = false; - } - if (!handled) { - handled = testForPrefixes(actionString); - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Proceed to the "test" screen if possible. - */ - private void nextScreen() { - String feedback = LinksScreen.feedback(); - if (feedback.isEmpty()) { - setEnabled(false); - InputMode test = InputMode.findMode("test"); - test.setEnabled(true); - } - } - - /** - * Go back to the "torso" screen. - */ - private void previousScreen() { - setEnabled(false); - InputMode bones = InputMode.findMode("torso"); - bones.setEnabled(true); - } - - /** - * Test an ongoing action for prefixes. - * - * @param actionString textual description of the action (not null) - * @return true if the action is handled, otherwise false - */ - private static boolean testForPrefixes(String actionString) { - boolean handled = true; - LinksScreen screen = DacWizard.findAppState(LinksScreen.class); - String arg; - - if (actionString.startsWith("select centerHeuristic ")) { - arg = MyString.remainder(actionString, "select centerHeuristic "); - CenterHeuristic heuristic = CenterHeuristic.valueOf(arg); - screen.selectCenterHeuristic(heuristic); - - } else if (actionString.startsWith("select rotationOrder ")) { - arg = MyString.remainder(actionString, "select rotationOrder "); - RotationOrder axisOrder; - if (arg.equals("sixdof")) { - axisOrder = null; - } else { - axisOrder = RotationOrder.valueOf(arg); - } - screen.selectRotationOrder(axisOrder); - - } else if (actionString.startsWith("select shapeHeuristic ")) { - arg = MyString.remainder(actionString, "select shapeHeuristic "); - ShapeHeuristic heuristic = ShapeHeuristic.valueOf(arg); - screen.selectShapeHeuristic(heuristic); - - } else if (actionString.startsWith("set massParameter ")) { - arg = MyString.remainder(actionString, "set massParameter "); - float value = Float.parseFloat(arg); - screen.setMassParameter(value); - - } else if (actionString.startsWith("set shapeScale ")) { - arg = MyString.remainder(actionString, "set shapeScale "); - Vector3f factors = MyVector3f.parse(arg); - if (factors != null && MyVector3f.isAllPositive(factors)) { - screen.setShapeScale(factors); - } - - } else { - handled = false; - } - - return handled; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.KeyInput; +import com.jme3.math.Vector3f; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyVector3f; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "links" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class LinksMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(LinksMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized input mode. + */ + LinksMode() { + super("links"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.previousScreen, KeyInput.KEY_PGUP); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + bind(Action.nextScreen, KeyInput.KEY_PGDN); + + bind(Action.previousScreen, KeyInput.KEY_B); + bind(Action.nextScreen, KeyInput.KEY_N); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Set the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = true; + if (ongoing) { + LinksScreen screen = DacWizard.findAppState(LinksScreen.class); + switch (actionString) { + case Action.nextMassHeuristic: + screen.nextMassHeuristic(); + break; + + case Action.nextScreen: + nextScreen(); + break; + + case Action.previousScreen: + previousScreen(); + break; + + case Action.selectCenterHeuristic: + screen.selectCenterHeuristic(); + break; + + case Action.selectRotationOrder: + screen.selectRotationOrder(); + break; + + case Action.selectShapeHeuristic: + screen.selectShapeHeuristic(); + break; + + case Action.setMassParameter: + screen.setMassParameter(); + break; + + case Action.setShapeScale: + screen.setShapeScale(); + break; + + case Action.toggleAngleMode: + DacWizard.getModel().toggleAngleMode(); + break; + + default: + handled = false; + } + if (!handled) { + handled = testForPrefixes(actionString); + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Proceed to the "test" screen if possible. + */ + private void nextScreen() { + String feedback = LinksScreen.feedback(); + if (feedback.isEmpty()) { + setEnabled(false); + InputMode test = InputMode.findMode("test"); + test.setEnabled(true); + } + } + + /** + * Go back to the "torso" screen. + */ + private void previousScreen() { + setEnabled(false); + InputMode bones = InputMode.findMode("torso"); + bones.setEnabled(true); + } + + /** + * Test an ongoing action for prefixes. + * + * @param actionString textual description of the action (not null) + * @return true if the action is handled, otherwise false + */ + private static boolean testForPrefixes(String actionString) { + boolean handled = true; + LinksScreen screen = DacWizard.findAppState(LinksScreen.class); + String arg; + + if (actionString.startsWith("select centerHeuristic ")) { + arg = MyString.remainder(actionString, "select centerHeuristic "); + CenterHeuristic heuristic = CenterHeuristic.valueOf(arg); + screen.selectCenterHeuristic(heuristic); + + } else if (actionString.startsWith("select rotationOrder ")) { + arg = MyString.remainder(actionString, "select rotationOrder "); + RotationOrder axisOrder; + if (arg.equals("sixdof")) { + axisOrder = null; + } else { + axisOrder = RotationOrder.valueOf(arg); + } + screen.selectRotationOrder(axisOrder); + + } else if (actionString.startsWith("select shapeHeuristic ")) { + arg = MyString.remainder(actionString, "select shapeHeuristic "); + ShapeHeuristic heuristic = ShapeHeuristic.valueOf(arg); + screen.selectShapeHeuristic(heuristic); + + } else if (actionString.startsWith("set massParameter ")) { + arg = MyString.remainder(actionString, "set massParameter "); + float value = Float.parseFloat(arg); + screen.setMassParameter(value); + + } else if (actionString.startsWith("set shapeScale ")) { + arg = MyString.remainder(actionString, "set shapeScale "); + Vector3f factors = MyVector3f.parse(arg); + if (factors != null && MyVector3f.isAllPositive(factors)) { + screen.setShapeScale(factors); + } + + } else { + handled = false; + } + + return handled; + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksScreen.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksScreen.java index e43549184..f10b9cde9 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksScreen.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/LinksScreen.java @@ -1,663 +1,663 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DacConfiguration; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import de.lessvoid.nifty.NiftyEventSubscriber; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.controls.SliderChangedEvent; -import de.lessvoid.nifty.controls.TreeBox; -import de.lessvoid.nifty.controls.TreeItem; -import de.lessvoid.nifty.elements.Element; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.InitialState; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.nifty.PopupMenuBuilder; -import jme3utilities.nifty.SliderTransform; -import jme3utilities.nifty.dialog.AllowNull; -import jme3utilities.nifty.dialog.DialogController; -import jme3utilities.nifty.dialog.FloatDialog; -import jme3utilities.nifty.dialog.VectorDialog; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "links" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -public class LinksScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(LinksScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of GUI button to proceed to the "test" screen - */ - private Element nextElement; - /** - * TreeBox to display links in the hierarchy - */ - private TreeBox treeBox; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - LinksScreen() { - super("links", "Interface/Nifty/screens/wizard/links.xml", - InitialState.Disabled); - } - // ************************************************************************* - // new methods exposed - - /** - * Determine user feedback (if any) regarding the "next screen" action. - * - * @return "" if ready to proceed, otherwise an explanatory message - */ - static String feedback() { - return ""; - } - - /** - * Callback handler that Nifty invokes after a slider changes. - * - * @param sliderId Nifty element ID of the slider (not null) - * @param event details of the event (not null, ignored) - */ - @NiftyEventSubscriber(pattern = ".*Slider") - public void linksScreenSliderChanged( - final String sliderId, final SliderChangedEvent event) { - Validate.nonNull(sliderId, "slider ID"); - assert sliderId.endsWith("Slider") : sliderId; - Validate.nonNull(event, "event"); - - if (!isIgnoreGuiChanges() && hasStarted()) { - readSliders(); - } - } - - /** - * Handle a "next massHeuristic" action. - */ - void nextMassHeuristic() { - LinkConfig config = config(); - float massParameter = config.massParameter(); - MassHeuristic massHeuristic = config.massHeuristic(); - if (massHeuristic == MassHeuristic.Mass) { - massHeuristic = MassHeuristic.Density; - } else { - massHeuristic = MassHeuristic.Mass; - } - ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); - Vector3f shapeScale = config.shapeScale(null); - CenterHeuristic centerHeuristic = config.centerHeuristic(); - RotationOrder axisOrder = config.rotationOrder(); - - config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, - shapeScale, centerHeuristic, axisOrder); - setConfig(config); - } - - /** - * Handle a "select centerHeuristic" action with no argument. - */ - void selectCenterHeuristic() { - PopupMenuBuilder builder = new PopupMenuBuilder(); - - LinkConfig config = config(); - String boneName = selectedLink(); - CenterHeuristic selected = config.centerHeuristic(); - for (CenterHeuristic heuristic : CenterHeuristic.values()) { - if (boneName.equals(DacConfiguration.torsoName) - && heuristic == CenterHeuristic.Joint) { - continue; - } - if (heuristic != selected) { - String name = heuristic.toString(); - builder.add(name); - } - } - - showPopupMenu("select centerHeuristic ", builder); - } - - /** - * Handle a "select centerHeuristic" action with an argument. - * - * @param heuristic the desired heuristic (not null) - */ - void selectCenterHeuristic(CenterHeuristic heuristic) { - LinkConfig config = config(); - float massParameter = config.massParameter(); - MassHeuristic massHeuristic = config.massHeuristic(); - ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); - Vector3f shapeScale = config.shapeScale(null); - RotationOrder axisOrder = config.rotationOrder(); - - config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, - shapeScale, heuristic, axisOrder); - setConfig(config); - } - - /** - * Determine which PhysicsLink is currently selected. - * - * @return the bone/torso name, or null if none selected - */ - String selectedLink() { - String result = null; - List> selectedLinks = treeBox.getSelection(); - if (!selectedLinks.isEmpty()) { - assert selectedLinks.size() == 1; - TreeItem selectedItem = selectedLinks.get(0); - LinkValue value = selectedItem.getValue(); - result = value.boneName(); - } - - return result; - } - - /** - * Handle a "select rotationOrder" action without an argument. - */ - void selectRotationOrder() { - PopupMenuBuilder builder = new PopupMenuBuilder(); - - LinkConfig config = config(); - RotationOrder selected = config.rotationOrder(); - if (selected != null) { - builder.add("sixdof"); - } - for (RotationOrder axisOrder : RotationOrder.values()) { - if (axisOrder != selected) { - String name = axisOrder.toString(); - builder.add(name); - } - } - - showPopupMenu("select rotationOrder ", builder); - } - - /** - * Handle a "select rotationOrder" action with an argument. - * - * @param axisOrder may be null - */ - void selectRotationOrder(RotationOrder axisOrder) { - LinkConfig config = config(); - float massParameter = config.massParameter(); - MassHeuristic massHeuristic = config.massHeuristic(); - ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); - Vector3f shapeScale = config.shapeScale(null); - CenterHeuristic centerHeuristic = config.centerHeuristic(); - - config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, - shapeScale, centerHeuristic, axisOrder); - setConfig(config); - } - - /** - * Handle a "select shapeHeuristic" action without an argument. - */ - void selectShapeHeuristic() { - PopupMenuBuilder builder = new PopupMenuBuilder(); - - LinkConfig config = config(); - ShapeHeuristic selected = config.shapeHeuristic(); - for (ShapeHeuristic heuristic : ShapeHeuristic.values()) { - if (heuristic != selected) { - String name = heuristic.toString(); - builder.add(name); - } - } - - showPopupMenu("select shapeHeuristic ", builder); - } - - /** - * Handle a "select shapeHeuristic" action with an argument. - * - * @param heuristic the desired heuristic (not null) - */ - void selectShapeHeuristic(ShapeHeuristic heuristic) { - LinkConfig config = config(); - float massParameter = config.massParameter(); - MassHeuristic massHeuristic = config.massHeuristic(); - Vector3f shapeScale = config.shapeScale(null); - CenterHeuristic centerHeuristic = config.centerHeuristic(); - RotationOrder axisOrder = config.rotationOrder(); - - config = new LinkConfig(massParameter, massHeuristic, heuristic, - shapeScale, centerHeuristic, axisOrder); - setConfig(config); - } - - /** - * Handle a "set massParameter" action without an argument. - */ - void setMassParameter() { - LinkConfig config = config(); - float oldParameter = config.massParameter(); - String defaultText = Float.toString(oldParameter); - String actionPrefix = "set massParameter "; - DialogController controller = new FloatDialog( - "Set", Float.MIN_VALUE, Float.MAX_VALUE, AllowNull.No); - showTextEntryDialog("Enter the mass-parameter value:", defaultText, - actionPrefix, controller); - } - - /** - * Handle a "set massParameter" action with an argument. - * - * @param value the desired parameter value (>0) - */ - void setMassParameter(float value) { - LinkConfig config = config(); - MassHeuristic massHeuristic = config.massHeuristic(); - ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); - Vector3f shapeScale = config.shapeScale(null); - CenterHeuristic centerHeuristic = config.centerHeuristic(); - RotationOrder axisOrder = config.rotationOrder(); - - config = new LinkConfig(value, massHeuristic, shapeHeuristic, - shapeScale, centerHeuristic, axisOrder); - setConfig(config); - } - - /** - * Handle a "set shapeScale" action without an argument. - */ - void setShapeScale() { - LinkConfig config = config(); - Vector3f oldParameter = config.shapeScale(null); - String defaultText = oldParameter.toString(); - String actionPrefix = "set shapeScale "; - DialogController controller = new VectorDialog("Set", 3, AllowNull.No); - showTextEntryDialog("Enter the shapeScale value:", defaultText, - actionPrefix, controller); - } - - /** - * Handle a "set shapeScale" action with an argument. - * - * @param scaleFactors the desired scale factors (not null, no negative - * component, unaffected) - */ - void setShapeScale(Vector3f scaleFactors) { - LinkConfig config = config(); - MassHeuristic massHeuristic = config.massHeuristic(); - float massParameter = config.massParameter(); - ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); - CenterHeuristic centerHeuristic = config.centerHeuristic(); - RotationOrder axisOrder = config.rotationOrder(); - - config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, - scaleFactors, centerHeuristic, axisOrder); - setConfig(config); - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("links"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - @SuppressWarnings("unchecked") - public void onStartScreen() { - super.onStartScreen(); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - - this.treeBox = getScreen().findNiftyControl("hierarchy", TreeBox.class); - if (treeBox == null) { - throw new RuntimeException("missing GUI control: hierarchy"); - } - - TreeItem rootItem = new TreeItem<>(); - rootItem.setExpanded(true); - - // Create an item for the torso. - LinkValue linkItem = new LinkValue(DacConfiguration.torsoName); - TreeItem torsoItem = new TreeItem<>(linkItem); - torsoItem.setExpanded(true); - - // Create an item for each linked bone in the hierarchy. - Model model = DacWizard.getModel(); - int[] linkedBoneIndices = model.listLinkedBones(); - int numLinkedBones = linkedBoneIndices.length; - TreeItem[] lbItems = new TreeItem[numLinkedBones]; - for (int lbIndex = 0; lbIndex < numLinkedBones; ++lbIndex) { - int boneIndex = linkedBoneIndices[lbIndex]; - String boneName = model.boneName(boneIndex); - linkItem = new LinkValue(boneName); - lbItems[lbIndex] = new TreeItem<>(linkItem); - lbItems[lbIndex].setExpanded(true); - } - - // Parent each item. - for (int childLbi = 0; childLbi < numLinkedBones; ++childLbi) { - TreeItem childItem = lbItems[childLbi]; - LinkValue childValue = childItem.getValue(); - String childName = childValue.boneName(); - String parentName = model.linkedBoneParentName(childName); - if (parentName.equals(DacConfiguration.torsoName)) { - torsoItem.addTreeItem(childItem); - } else { // parent is a BoneLink - TreeItem parentItem - = findLinkedBoneItem(parentName, lbItems); - parentItem.addTreeItem(childItem); - } - } - rootItem.addTreeItem(torsoItem); - treeBox.setTree(rootItem); - - // Initialize the selection. - String selected = model.selectedLink(); - if (selected.equals(DacConfiguration.torsoName)) { - treeBox.selectItem(torsoItem); - } else { // a BoneLink is selected - TreeItem item = findLinkedBoneItem(selected, lbItems); - treeBox.selectItem(item); - } - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - Model model = DacWizard.getModel(); - List> selectedLinks = treeBox.getSelection(); - String boneName; - if (selectedLinks.isEmpty()) { - boneName = model.selectedLink(); - } else { // an item is selected - assert selectedLinks.size() == 1 : selectedLinks.size(); - TreeItem selectedItem = selectedLinks.get(0); - LinkValue value = selectedItem.getValue(); - boneName = value.boneName(); - model.selectLink(boneName); - } - - String xRangeStatus = ""; - String yRangeStatus = ""; - String zRangeStatus = ""; - if (boneName.equals(DacConfiguration.torsoName)) { - setSliderEnabled("minX", false); - setSliderEnabled("maxX", false); - setSliderEnabled("minY", false); - setSliderEnabled("maxY", false); - setSliderEnabled("minZ", false); - setSliderEnabled("maxZ", false); - } else { // a BoneLink is selected - RangeOfMotion rom = model.rom(boneName); - xRangeStatus = describe(rom, PhysicsSpace.AXIS_X); - yRangeStatus = describe(rom, PhysicsSpace.AXIS_Y); - zRangeStatus = describe(rom, PhysicsSpace.AXIS_Z); - setAxisSliders("minX", "maxX", rom, PhysicsSpace.AXIS_X); - setAxisSliders("minY", "maxY", rom, PhysicsSpace.AXIS_Y); - setAxisSliders("minZ", "maxZ", rom, PhysicsSpace.AXIS_Z); - } - setStatusText("xRangeStatus", xRangeStatus); - setStatusText("yRangeStatus", yRangeStatus); - setStatusText("zRangeStatus", zRangeStatus); - - LinkConfig config = model.config(boneName); - String centerHeuristicButton = config.centerHeuristic().toString(); - setButtonText("centerHeuristic", centerHeuristicButton); - - MassHeuristic massHeuristic = config.massHeuristic(); - String massHeuristicButton = massHeuristic.toString().toLowerCase(); - setButtonText("massHeuristic", massHeuristicButton); - - float massParameter = config.massParameter(); - String massParameterButton = MyString.describe(massParameter); - setButtonText("massParameter", massParameterButton); - - String shapeHeuristicButton = config.shapeHeuristic().toString(); - setButtonText("shapeHeuristic", shapeHeuristicButton); - - Vector3f shapeScale = config.shapeScale(null); - String shapeScaleButton = MyVector3f.describe(shapeScale); - setButtonText("shapeScale", shapeScaleButton); - - RotationOrder order = config.rotationOrder(); - String rotationOrderButtonText; - if (order == null) { - rotationOrderButtonText = "sixdof"; - } else { - rotationOrderButtonText = order.toString(); - } - setButtonText("rotationOrder", rotationOrderButtonText); - - String feedback = feedback(); - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - - AngleMode angleMode = model.angleMode(); - String angleModeText = angleMode.toString(); - setButtonText("angleMode", angleModeText); - } - // ************************************************************************* - // private methods - - /** - * Read the configuration of the selected link. - * - * @return the configuration (not null) - */ - private LinkConfig config() { - Model model = DacWizard.getModel(); - String boneName = selectedLink(); - LinkConfig result = model.config(boneName); - - return result; - } - - /** - * Describe one axis of a joint's range of motion. - * - * @param rom (not null) - * @param axisIndex axisIndex which axis: 0→X, 1→Y, 2→Z - * @return descriptive text (not null, not empty) - */ - private static String describe(RangeOfMotion rom, int axisIndex) { - float maxRadians = rom.getMaxRotation(axisIndex); - float minRadians = rom.getMinRotation(axisIndex); - - String text; - AngleMode angleMode = DacWizard.getModel().angleMode(); - switch (angleMode) { - case Degrees: - float maxDegrees = MyMath.toDegrees(maxRadians); - float minDegrees = MyMath.toDegrees(minRadians); - int max = Math.round(maxDegrees); - int min = Math.round(minDegrees); - text = String.format("%+d to %+d degrees", min, max); - break; - - case Radians: - text = String.format( - "%+.2f to %+.2f rad", minRadians, maxRadians); - break; - - default: - throw new IllegalStateException("angleMode = " + angleMode); - } - - return text; - } - - /** - * Find the item for the named BoneLink. - * - * @param boneName the bone name of the link (not null, not empty) - * @param linkedBoneItems the array of items (not null, not empty, - * unaffected) - * @return the pre-existing item, or null if not found - */ - private static TreeItem findLinkedBoneItem( - String boneName, TreeItem[] linkedBoneItems) { - assert boneName != null; - assert !boneName.isEmpty(); - - TreeItem result; - for (TreeItem linkedBoneItem : linkedBoneItems) { - result = linkedBoneItem; - LinkValue value = result.getValue(); - if (boneName.equals(value.boneName())) { - return result; - } - } - - return null; - } - - /** - * Update the RangeOfMotion of the selected BoneLink based on slider - * positions. - */ - private void readSliders() { - float maxX = readSlider("maxX", SliderTransform.None); - maxX = MyMath.toRadians(maxX); - - float minX = readSlider("minX", SliderTransform.None); - minX = MyMath.toRadians(minX); - - float maxY = readSlider("maxY", SliderTransform.None); - maxY = MyMath.toRadians(maxY); - - float minY = readSlider("minY", SliderTransform.None); - minY = MyMath.toRadians(minY); - - float maxZ = readSlider("maxZ", SliderTransform.None); - maxZ = MyMath.toRadians(maxZ); - - float minZ = readSlider("minZ", SliderTransform.None); - minZ = MyMath.toRadians(minZ); - - RangeOfMotion rom - = new RangeOfMotion(maxX, minX, maxY, minY, maxZ, minZ); - - Model model = DacWizard.getModel(); - String boneName = model.selectedLink(); - model.setRom(boneName, rom); - } - - /** - * Reposition and enable the named sliders to reflect the indexed axis of - * the specified RangeOfMotion. - * - * @param minSliderName the name of the slider for the minimum rotation (not - * null, not empty) - * @param maxSliderName the name of the slider for the maximum rotation (not - * null, not empty) - * @param rom (not null) - * @param axisIndex which axis: 0→X, 1→Y, 2→Z - */ - private void setAxisSliders(String minSliderName, String maxSliderName, - RangeOfMotion rom, int axisIndex) { - float maxRadians = rom.getMaxRotation(axisIndex); - float maxDegrees = MyMath.toDegrees(maxRadians); - setSliderEnabled(maxSliderName, true); - setSlider(maxSliderName, SliderTransform.None, maxDegrees); - - float minRadians = rom.getMinRotation(axisIndex); - float minDegrees = MyMath.toDegrees(minRadians); - setSliderEnabled(minSliderName, true); - setSlider(minSliderName, SliderTransform.None, minDegrees); - } - - /** - * Alter the configuration of the selected link. - * - * @param config the desired configuration (not null) - */ - private void setConfig(LinkConfig config) { - Model model = DacWizard.getModel(); - String boneName = selectedLink(); - model.setConfig(boneName, config); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DacConfiguration; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import de.lessvoid.nifty.NiftyEventSubscriber; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.controls.SliderChangedEvent; +import de.lessvoid.nifty.controls.TreeBox; +import de.lessvoid.nifty.controls.TreeItem; +import de.lessvoid.nifty.elements.Element; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.InitialState; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.nifty.PopupMenuBuilder; +import jme3utilities.nifty.SliderTransform; +import jme3utilities.nifty.dialog.AllowNull; +import jme3utilities.nifty.dialog.DialogController; +import jme3utilities.nifty.dialog.FloatDialog; +import jme3utilities.nifty.dialog.VectorDialog; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "links" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +public class LinksScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(LinksScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of GUI button to proceed to the "test" screen + */ + private Element nextElement; + /** + * TreeBox to display links in the hierarchy + */ + private TreeBox treeBox; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + LinksScreen() { + super("links", "Interface/Nifty/screens/wizard/links.xml", + InitialState.Disabled); + } + // ************************************************************************* + // new methods exposed + + /** + * Determine user feedback (if any) regarding the "next screen" action. + * + * @return "" if ready to proceed, otherwise an explanatory message + */ + static String feedback() { + return ""; + } + + /** + * Callback handler that Nifty invokes after a slider changes. + * + * @param sliderId Nifty element ID of the slider (not null) + * @param event details of the event (not null, ignored) + */ + @NiftyEventSubscriber(pattern = ".*Slider") + public void linksScreenSliderChanged( + final String sliderId, final SliderChangedEvent event) { + Validate.nonNull(sliderId, "slider ID"); + assert sliderId.endsWith("Slider") : sliderId; + Validate.nonNull(event, "event"); + + if (!isIgnoreGuiChanges() && hasStarted()) { + readSliders(); + } + } + + /** + * Handle a "next massHeuristic" action. + */ + void nextMassHeuristic() { + LinkConfig config = config(); + float massParameter = config.massParameter(); + MassHeuristic massHeuristic = config.massHeuristic(); + if (massHeuristic == MassHeuristic.Mass) { + massHeuristic = MassHeuristic.Density; + } else { + massHeuristic = MassHeuristic.Mass; + } + ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); + Vector3f shapeScale = config.shapeScale(null); + CenterHeuristic centerHeuristic = config.centerHeuristic(); + RotationOrder axisOrder = config.rotationOrder(); + + config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, + shapeScale, centerHeuristic, axisOrder); + setConfig(config); + } + + /** + * Handle a "select centerHeuristic" action with no argument. + */ + void selectCenterHeuristic() { + PopupMenuBuilder builder = new PopupMenuBuilder(); + + LinkConfig config = config(); + String boneName = selectedLink(); + CenterHeuristic selected = config.centerHeuristic(); + for (CenterHeuristic heuristic : CenterHeuristic.values()) { + if (boneName.equals(DacConfiguration.torsoName) + && heuristic == CenterHeuristic.Joint) { + continue; + } + if (heuristic != selected) { + String name = heuristic.toString(); + builder.add(name); + } + } + + showPopupMenu("select centerHeuristic ", builder); + } + + /** + * Handle a "select centerHeuristic" action with an argument. + * + * @param heuristic the desired heuristic (not null) + */ + void selectCenterHeuristic(CenterHeuristic heuristic) { + LinkConfig config = config(); + float massParameter = config.massParameter(); + MassHeuristic massHeuristic = config.massHeuristic(); + ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); + Vector3f shapeScale = config.shapeScale(null); + RotationOrder axisOrder = config.rotationOrder(); + + config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, + shapeScale, heuristic, axisOrder); + setConfig(config); + } + + /** + * Determine which PhysicsLink is currently selected. + * + * @return the bone/torso name, or null if none selected + */ + String selectedLink() { + String result = null; + List> selectedLinks = treeBox.getSelection(); + if (!selectedLinks.isEmpty()) { + assert selectedLinks.size() == 1; + TreeItem selectedItem = selectedLinks.get(0); + LinkValue value = selectedItem.getValue(); + result = value.boneName(); + } + + return result; + } + + /** + * Handle a "select rotationOrder" action without an argument. + */ + void selectRotationOrder() { + PopupMenuBuilder builder = new PopupMenuBuilder(); + + LinkConfig config = config(); + RotationOrder selected = config.rotationOrder(); + if (selected != null) { + builder.add("sixdof"); + } + for (RotationOrder axisOrder : RotationOrder.values()) { + if (axisOrder != selected) { + String name = axisOrder.toString(); + builder.add(name); + } + } + + showPopupMenu("select rotationOrder ", builder); + } + + /** + * Handle a "select rotationOrder" action with an argument. + * + * @param axisOrder may be null + */ + void selectRotationOrder(RotationOrder axisOrder) { + LinkConfig config = config(); + float massParameter = config.massParameter(); + MassHeuristic massHeuristic = config.massHeuristic(); + ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); + Vector3f shapeScale = config.shapeScale(null); + CenterHeuristic centerHeuristic = config.centerHeuristic(); + + config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, + shapeScale, centerHeuristic, axisOrder); + setConfig(config); + } + + /** + * Handle a "select shapeHeuristic" action without an argument. + */ + void selectShapeHeuristic() { + PopupMenuBuilder builder = new PopupMenuBuilder(); + + LinkConfig config = config(); + ShapeHeuristic selected = config.shapeHeuristic(); + for (ShapeHeuristic heuristic : ShapeHeuristic.values()) { + if (heuristic != selected) { + String name = heuristic.toString(); + builder.add(name); + } + } + + showPopupMenu("select shapeHeuristic ", builder); + } + + /** + * Handle a "select shapeHeuristic" action with an argument. + * + * @param heuristic the desired heuristic (not null) + */ + void selectShapeHeuristic(ShapeHeuristic heuristic) { + LinkConfig config = config(); + float massParameter = config.massParameter(); + MassHeuristic massHeuristic = config.massHeuristic(); + Vector3f shapeScale = config.shapeScale(null); + CenterHeuristic centerHeuristic = config.centerHeuristic(); + RotationOrder axisOrder = config.rotationOrder(); + + config = new LinkConfig(massParameter, massHeuristic, heuristic, + shapeScale, centerHeuristic, axisOrder); + setConfig(config); + } + + /** + * Handle a "set massParameter" action without an argument. + */ + void setMassParameter() { + LinkConfig config = config(); + float oldParameter = config.massParameter(); + String defaultText = Float.toString(oldParameter); + String actionPrefix = "set massParameter "; + DialogController controller = new FloatDialog( + "Set", Float.MIN_VALUE, Float.MAX_VALUE, AllowNull.No); + showTextEntryDialog("Enter the mass-parameter value:", defaultText, + actionPrefix, controller); + } + + /** + * Handle a "set massParameter" action with an argument. + * + * @param value the desired parameter value (>0) + */ + void setMassParameter(float value) { + LinkConfig config = config(); + MassHeuristic massHeuristic = config.massHeuristic(); + ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); + Vector3f shapeScale = config.shapeScale(null); + CenterHeuristic centerHeuristic = config.centerHeuristic(); + RotationOrder axisOrder = config.rotationOrder(); + + config = new LinkConfig(value, massHeuristic, shapeHeuristic, + shapeScale, centerHeuristic, axisOrder); + setConfig(config); + } + + /** + * Handle a "set shapeScale" action without an argument. + */ + void setShapeScale() { + LinkConfig config = config(); + Vector3f oldParameter = config.shapeScale(null); + String defaultText = oldParameter.toString(); + String actionPrefix = "set shapeScale "; + DialogController controller = new VectorDialog("Set", 3, AllowNull.No); + showTextEntryDialog("Enter the shapeScale value:", defaultText, + actionPrefix, controller); + } + + /** + * Handle a "set shapeScale" action with an argument. + * + * @param scaleFactors the desired scale factors (not null, no negative + * component, unaffected) + */ + void setShapeScale(Vector3f scaleFactors) { + LinkConfig config = config(); + MassHeuristic massHeuristic = config.massHeuristic(); + float massParameter = config.massParameter(); + ShapeHeuristic shapeHeuristic = config.shapeHeuristic(); + CenterHeuristic centerHeuristic = config.centerHeuristic(); + RotationOrder axisOrder = config.rotationOrder(); + + config = new LinkConfig(massParameter, massHeuristic, shapeHeuristic, + scaleFactors, centerHeuristic, axisOrder); + setConfig(config); + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("links"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + @SuppressWarnings("unchecked") + public void onStartScreen() { + super.onStartScreen(); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + + this.treeBox = getScreen().findNiftyControl("hierarchy", TreeBox.class); + if (treeBox == null) { + throw new RuntimeException("missing GUI control: hierarchy"); + } + + TreeItem rootItem = new TreeItem<>(); + rootItem.setExpanded(true); + + // Create an item for the torso. + LinkValue linkItem = new LinkValue(DacConfiguration.torsoName); + TreeItem torsoItem = new TreeItem<>(linkItem); + torsoItem.setExpanded(true); + + // Create an item for each linked bone in the hierarchy. + Model model = DacWizard.getModel(); + int[] linkedBoneIndices = model.listLinkedBones(); + int numLinkedBones = linkedBoneIndices.length; + TreeItem[] lbItems = new TreeItem[numLinkedBones]; + for (int lbIndex = 0; lbIndex < numLinkedBones; ++lbIndex) { + int boneIndex = linkedBoneIndices[lbIndex]; + String boneName = model.boneName(boneIndex); + linkItem = new LinkValue(boneName); + lbItems[lbIndex] = new TreeItem<>(linkItem); + lbItems[lbIndex].setExpanded(true); + } + + // Parent each item. + for (int childLbi = 0; childLbi < numLinkedBones; ++childLbi) { + TreeItem childItem = lbItems[childLbi]; + LinkValue childValue = childItem.getValue(); + String childName = childValue.boneName(); + String parentName = model.linkedBoneParentName(childName); + if (parentName.equals(DacConfiguration.torsoName)) { + torsoItem.addTreeItem(childItem); + } else { // parent is a BoneLink + TreeItem parentItem + = findLinkedBoneItem(parentName, lbItems); + parentItem.addTreeItem(childItem); + } + } + rootItem.addTreeItem(torsoItem); + treeBox.setTree(rootItem); + + // Initialize the selection. + String selected = model.selectedLink(); + if (selected.equals(DacConfiguration.torsoName)) { + treeBox.selectItem(torsoItem); + } else { // a BoneLink is selected + TreeItem item = findLinkedBoneItem(selected, lbItems); + treeBox.selectItem(item); + } + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + Model model = DacWizard.getModel(); + List> selectedLinks = treeBox.getSelection(); + String boneName; + if (selectedLinks.isEmpty()) { + boneName = model.selectedLink(); + } else { // an item is selected + assert selectedLinks.size() == 1 : selectedLinks.size(); + TreeItem selectedItem = selectedLinks.get(0); + LinkValue value = selectedItem.getValue(); + boneName = value.boneName(); + model.selectLink(boneName); + } + + String xRangeStatus = ""; + String yRangeStatus = ""; + String zRangeStatus = ""; + if (boneName.equals(DacConfiguration.torsoName)) { + setSliderEnabled("minX", false); + setSliderEnabled("maxX", false); + setSliderEnabled("minY", false); + setSliderEnabled("maxY", false); + setSliderEnabled("minZ", false); + setSliderEnabled("maxZ", false); + } else { // a BoneLink is selected + RangeOfMotion rom = model.rom(boneName); + xRangeStatus = describe(rom, PhysicsSpace.AXIS_X); + yRangeStatus = describe(rom, PhysicsSpace.AXIS_Y); + zRangeStatus = describe(rom, PhysicsSpace.AXIS_Z); + setAxisSliders("minX", "maxX", rom, PhysicsSpace.AXIS_X); + setAxisSliders("minY", "maxY", rom, PhysicsSpace.AXIS_Y); + setAxisSliders("minZ", "maxZ", rom, PhysicsSpace.AXIS_Z); + } + setStatusText("xRangeStatus", xRangeStatus); + setStatusText("yRangeStatus", yRangeStatus); + setStatusText("zRangeStatus", zRangeStatus); + + LinkConfig config = model.config(boneName); + String centerHeuristicButton = config.centerHeuristic().toString(); + setButtonText("centerHeuristic", centerHeuristicButton); + + MassHeuristic massHeuristic = config.massHeuristic(); + String massHeuristicButton = massHeuristic.toString().toLowerCase(); + setButtonText("massHeuristic", massHeuristicButton); + + float massParameter = config.massParameter(); + String massParameterButton = MyString.describe(massParameter); + setButtonText("massParameter", massParameterButton); + + String shapeHeuristicButton = config.shapeHeuristic().toString(); + setButtonText("shapeHeuristic", shapeHeuristicButton); + + Vector3f shapeScale = config.shapeScale(null); + String shapeScaleButton = MyVector3f.describe(shapeScale); + setButtonText("shapeScale", shapeScaleButton); + + RotationOrder order = config.rotationOrder(); + String rotationOrderButtonText; + if (order == null) { + rotationOrderButtonText = "sixdof"; + } else { + rotationOrderButtonText = order.toString(); + } + setButtonText("rotationOrder", rotationOrderButtonText); + + String feedback = feedback(); + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + + AngleMode angleMode = model.angleMode(); + String angleModeText = angleMode.toString(); + setButtonText("angleMode", angleModeText); + } + // ************************************************************************* + // private methods + + /** + * Read the configuration of the selected link. + * + * @return the configuration (not null) + */ + private LinkConfig config() { + Model model = DacWizard.getModel(); + String boneName = selectedLink(); + LinkConfig result = model.config(boneName); + + return result; + } + + /** + * Describe one axis of a joint's range of motion. + * + * @param rom (not null) + * @param axisIndex axisIndex which axis: 0→X, 1→Y, 2→Z + * @return descriptive text (not null, not empty) + */ + private static String describe(RangeOfMotion rom, int axisIndex) { + float maxRadians = rom.getMaxRotation(axisIndex); + float minRadians = rom.getMinRotation(axisIndex); + + String text; + AngleMode angleMode = DacWizard.getModel().angleMode(); + switch (angleMode) { + case Degrees: + float maxDegrees = MyMath.toDegrees(maxRadians); + float minDegrees = MyMath.toDegrees(minRadians); + int max = Math.round(maxDegrees); + int min = Math.round(minDegrees); + text = String.format("%+d to %+d degrees", min, max); + break; + + case Radians: + text = String.format( + "%+.2f to %+.2f rad", minRadians, maxRadians); + break; + + default: + throw new IllegalStateException("angleMode = " + angleMode); + } + + return text; + } + + /** + * Find the item for the named BoneLink. + * + * @param boneName the bone name of the link (not null, not empty) + * @param linkedBoneItems the array of items (not null, not empty, + * unaffected) + * @return the pre-existing item, or null if not found + */ + private static TreeItem findLinkedBoneItem( + String boneName, TreeItem[] linkedBoneItems) { + assert boneName != null; + assert !boneName.isEmpty(); + + TreeItem result; + for (TreeItem linkedBoneItem : linkedBoneItems) { + result = linkedBoneItem; + LinkValue value = result.getValue(); + if (boneName.equals(value.boneName())) { + return result; + } + } + + return null; + } + + /** + * Update the RangeOfMotion of the selected BoneLink based on slider + * positions. + */ + private void readSliders() { + float maxX = readSlider("maxX", SliderTransform.None); + maxX = MyMath.toRadians(maxX); + + float minX = readSlider("minX", SliderTransform.None); + minX = MyMath.toRadians(minX); + + float maxY = readSlider("maxY", SliderTransform.None); + maxY = MyMath.toRadians(maxY); + + float minY = readSlider("minY", SliderTransform.None); + minY = MyMath.toRadians(minY); + + float maxZ = readSlider("maxZ", SliderTransform.None); + maxZ = MyMath.toRadians(maxZ); + + float minZ = readSlider("minZ", SliderTransform.None); + minZ = MyMath.toRadians(minZ); + + RangeOfMotion rom + = new RangeOfMotion(maxX, minX, maxY, minY, maxZ, minZ); + + Model model = DacWizard.getModel(); + String boneName = model.selectedLink(); + model.setRom(boneName, rom); + } + + /** + * Reposition and enable the named sliders to reflect the indexed axis of + * the specified RangeOfMotion. + * + * @param minSliderName the name of the slider for the minimum rotation (not + * null, not empty) + * @param maxSliderName the name of the slider for the maximum rotation (not + * null, not empty) + * @param rom (not null) + * @param axisIndex which axis: 0→X, 1→Y, 2→Z + */ + private void setAxisSliders(String minSliderName, String maxSliderName, + RangeOfMotion rom, int axisIndex) { + float maxRadians = rom.getMaxRotation(axisIndex); + float maxDegrees = MyMath.toDegrees(maxRadians); + setSliderEnabled(maxSliderName, true); + setSlider(maxSliderName, SliderTransform.None, maxDegrees); + + float minRadians = rom.getMinRotation(axisIndex); + float minDegrees = MyMath.toDegrees(minRadians); + setSliderEnabled(minSliderName, true); + setSlider(minSliderName, SliderTransform.None, minDegrees); + } + + /** + * Alter the configuration of the selected link. + * + * @param config the desired configuration (not null) + */ + private void setConfig(LinkConfig config) { + Model model = DacWizard.getModel(); + String boneName = selectedLink(); + model.setConfig(boneName, config); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadMode.java index 45bb354d1..d882880e4 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadMode.java @@ -1,234 +1,234 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.nifty.dialog.AllowNull; -import jme3utilities.nifty.dialog.DialogController; -import jme3utilities.nifty.dialog.FloatDialog; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "load" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class LoadMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(LoadMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized input mode. - */ - LoadMode() { - super("load"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); - bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); - - bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); - bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); - bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); - bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); - bindSignal("orbitLeft", KeyInput.KEY_A); - bindSignal("orbitRight", KeyInput.KEY_D); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - - bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); - bind(Action.toggleSkeleton, KeyInput.KEY_V); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - Model model = DacWizard.getModel(); - handled = true; - switch (actionString) { - case Action.load: - model.load(); - break; - - case Action.morePath: - model.morePath(); - break; - - case Action.moreRoot: - model.moreRoot(); - break; - - case Action.nextAnimation: - model.nextAnimation(); - break; - - case Action.nextScreen: - nextScreen(); - break; - - case Action.previousAnimation: - model.previousAnimation(); - break; - - case Action.previousScreen: - model.unload(); - previousScreen(); - break; - - case Action.setAnimationTime: - setAnimationTime(); - break; - - case Action.toggleSkeleton: - model.toggleShowingSkeleton(); - break; - - default: - handled = false; - } - - String prefix = Action.setAnimationTime + " "; - if (!handled && actionString.startsWith(prefix)) { - String argument = MyString.remainder(actionString, prefix); - float time = Float.parseFloat(argument); - model.setAnimationTime(time); - handled = true; - } - } - - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Proceed to the "bones" screen if possible. - */ - private void nextScreen() { - String feedback = LoadScreen.feedback(); - if (feedback.isEmpty()) { - setEnabled(false); - InputMode bones = InputMode.findMode("bones"); - bones.setEnabled(true); - } - } - - /** - * Go back to the "filePath" screen. - */ - private void previousScreen() { - setEnabled(false); - InputMode filePath = InputMode.findMode("filePath"); - filePath.setEnabled(true); - } - - /** - * Process a "set animationTime" action: display a dialog to enter a new - * animation time. - */ - private static void setAnimationTime() { - Model model = DacWizard.getModel(); - float duration = model.animationDuration(); - DialogController controller - = new FloatDialog("Set", 0f, duration, AllowNull.No); - - float oldTime = model.animationTime(); - String defaultText = Float.toString(oldTime); - - LoadScreen screen = DacWizard.findAppState(LoadScreen.class); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter the animation time (in seconds):", - defaultText, Action.setAnimationTime + " ", controller); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.nifty.dialog.AllowNull; +import jme3utilities.nifty.dialog.DialogController; +import jme3utilities.nifty.dialog.FloatDialog; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "load" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class LoadMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(LoadMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized input mode. + */ + LoadMode() { + super("load"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); + bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); + + bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); + bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); + bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); + bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); + bindSignal("orbitLeft", KeyInput.KEY_A); + bindSignal("orbitRight", KeyInput.KEY_D); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + + bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); + bind(Action.toggleSkeleton, KeyInput.KEY_V); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + Model model = DacWizard.getModel(); + handled = true; + switch (actionString) { + case Action.load: + model.load(); + break; + + case Action.morePath: + model.morePath(); + break; + + case Action.moreRoot: + model.moreRoot(); + break; + + case Action.nextAnimation: + model.nextAnimation(); + break; + + case Action.nextScreen: + nextScreen(); + break; + + case Action.previousAnimation: + model.previousAnimation(); + break; + + case Action.previousScreen: + model.unload(); + previousScreen(); + break; + + case Action.setAnimationTime: + setAnimationTime(); + break; + + case Action.toggleSkeleton: + model.toggleShowingSkeleton(); + break; + + default: + handled = false; + } + + String prefix = Action.setAnimationTime + " "; + if (!handled && actionString.startsWith(prefix)) { + String argument = MyString.remainder(actionString, prefix); + float time = Float.parseFloat(argument); + model.setAnimationTime(time); + handled = true; + } + } + + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Proceed to the "bones" screen if possible. + */ + private void nextScreen() { + String feedback = LoadScreen.feedback(); + if (feedback.isEmpty()) { + setEnabled(false); + InputMode bones = InputMode.findMode("bones"); + bones.setEnabled(true); + } + } + + /** + * Go back to the "filePath" screen. + */ + private void previousScreen() { + setEnabled(false); + InputMode filePath = InputMode.findMode("filePath"); + filePath.setEnabled(true); + } + + /** + * Process a "set animationTime" action: display a dialog to enter a new + * animation time. + */ + private static void setAnimationTime() { + Model model = DacWizard.getModel(); + float duration = model.animationDuration(); + DialogController controller + = new FloatDialog("Set", 0f, duration, AllowNull.No); + + float oldTime = model.animationTime(); + String defaultText = Float.toString(oldTime); + + LoadScreen screen = DacWizard.findAppState(LoadScreen.class); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter the animation time (in seconds):", + defaultText, Action.setAnimationTime + " ", controller); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadScreen.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadScreen.java index 19167df5c..f122533cf 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadScreen.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/LoadScreen.java @@ -1,325 +1,325 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.animation.Skeleton; -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.scene.Spatial; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.elements.Element; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InitialState; -import jme3utilities.MyString; -import jme3utilities.debug.SkeletonVisualizer; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "load" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class LoadScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(LoadScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of the GUI button to proceed to the "bones" screen - */ - private Element nextElement; - /** - * animation time of the pose being viewed (in seconds) - */ - private float viewedAnimationTime; - /** - * root spatial of the C-G model being viewed, or null for none - */ - private Spatial viewedSpatial; - /** - * clip/animation name of the pose being viewed - */ - private String viewedAnimationName; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - LoadScreen() { - super("load", "Interface/Nifty/screens/wizard/load.xml", - InitialState.Disabled); - } - // ************************************************************************* - // new methods exposed - - /** - * Determine user feedback (if any) regarding the "next screen" action. - * - * @return an empty string if ready to proceed, otherwise an explanatory - * message - */ - static String feedback() { - Model model = DacWizard.getModel(); - int numDacs = model.countDacs(); - int numSkeletonControls = model.countSControls(); - Spatial nextSpatial = model.getRootSpatial(); - String loadException = model.loadExceptionString(); - - String result; - if (!loadException.isEmpty()) { - result = loadException; - } else if (nextSpatial == null) { - result = "The model hasn't been loaded yet."; - } else if (numSkeletonControls == 0) { - result = "The model lacks a skinning/skeleton control."; - } else if (numSkeletonControls > 1) { - result = String.format( - "The model has %d skinning/skeleton controls.", - numSkeletonControls); - - } else if (model.countBones() < 1) { - if (model.findSkeleton() == null) { // new animation system - result = "The model's Armature lacks joints."; - } else { // old animation system - result = "The model's Skeleton lacks bones."; - } - - } else if (numDacs > 1) { - result = String.format("The model has %d DACs.", numDacs); - } else { - result = model.validationFeedback(); - } - - return result; - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("load"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - - DacWizard wizard = DacWizard.getApplication(); - wizard.clearScene(); - this.viewedSpatial = null; - this.viewedAnimationName = null; - this.viewedAnimationTime = Float.NaN; - - Model model = DacWizard.getModel(); - model.setShowingMeshes(true); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - updateFeedback(); - updatePath(); - updateToggleButton(); - - // Update the 3-D scene. - Model model = DacWizard.getModel(); - Spatial nextSpatial = model.getRootSpatial(); - String nextAnimationName = model.animationName(); - float nextAnimationTime = model.animationTime(); - if (nextSpatial != viewedSpatial - || !nextAnimationName.equals(viewedAnimationName) - || nextAnimationTime != viewedAnimationTime) { - DacWizard wizard = DacWizard.getApplication(); - wizard.clearScene(); - - this.viewedSpatial = nextSpatial; - this.viewedAnimationName = nextAnimationName; - this.viewedAnimationTime = nextAnimationTime; - - if (nextSpatial != null) { - Spatial cgModel = Heart.deepCopy(nextSpatial); - wizard.makeScene(cgModel, nextAnimationName, nextAnimationTime); - } - } - - updatePosingControls(); - } - // ************************************************************************* - // private methods - - /** - * Update the feedback line and the load/next buttons. - */ - private void updateFeedback() { - Model model = DacWizard.getModel(); - Spatial nextSpatial = model.getRootSpatial(); - String loadException = model.loadExceptionString(); - - String loadButton = ""; - if (loadException.isEmpty() && nextSpatial == null) { - loadButton = "Load and preview"; - } - setButtonText("load", loadButton); - - String feedback = feedback(); - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - } - - /** - * Update the posing controls. - */ - private void updatePosingControls() { - String anText = ""; - String atText = ""; - String naText = ""; - String paText = ""; - - if (viewedSpatial != null) { - Model model = DacWizard.getModel(); - int numAnimations = model.countAnimations(); - if (numAnimations > 0) { - paText = "-"; - naText = "+"; - } - - float duration = model.animationDuration(); - if (duration > 0f) { - atText = Float.toString(viewedAnimationTime) + " seconds"; - } - - anText = viewedAnimationName; - if (!anText.equals(DacWizard.bindPoseName)) { - anText = MyString.quote(anText); - } - } - - setStatusText("animationName", anText); - setButtonText("animationTime", atText); - setButtonText("nextAnimation", naText); - setButtonText("previousAnimation", paText); - } - - /** - * Update the path status and "+" buttons. - */ - private void updatePath() { - Model model = DacWizard.getModel(); - - String assetPath = model.assetPath(); - String assetRoot = model.assetRoot(); - String[] pathComponents = assetPath.split("/"); - String[] rootComponents = assetRoot.split("/"); - - String morePathButton = ""; - String moreRootButton = ""; - if (pathComponents.length > 2) { - moreRootButton = "+"; - } - if (rootComponents.length > 1) { - morePathButton = "+"; - } - - setButtonText("morePath", morePathButton); - setButtonText("moreRoot", moreRootButton); - - setStatusText("assetPath", " " + assetPath); - setStatusText("assetRoot", " " + assetRoot); - } - - /** - * Update the button to toggle skeleton visualization. - */ - private void updateToggleButton() { - String buttonText = ""; - - DacWizard app = DacWizard.getApplication(); - SkeletonVisualizer sv = app.findSkeletonVisualizer(); - Model model = DacWizard.getModel(); - Spatial root = model.getRootSpatial(); - if (sv != null && root != null) { - boolean isShown = model.isShowingSkeleton(); - sv.setEnabled(isShown); - - Skeleton skeleton = model.findSkeleton(); - String armature = (skeleton == null) ? "armature" : "skeleton"; - if (isShown) { - buttonText = "Hide " + armature; - } else { - buttonText = "Show " + armature; - } - } - - setButtonText("skeleton", buttonText); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.animation.Skeleton; +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.scene.Spatial; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.elements.Element; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InitialState; +import jme3utilities.MyString; +import jme3utilities.debug.SkeletonVisualizer; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "load" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class LoadScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(LoadScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of the GUI button to proceed to the "bones" screen + */ + private Element nextElement; + /** + * animation time of the pose being viewed (in seconds) + */ + private float viewedAnimationTime; + /** + * root spatial of the C-G model being viewed, or null for none + */ + private Spatial viewedSpatial; + /** + * clip/animation name of the pose being viewed + */ + private String viewedAnimationName; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + LoadScreen() { + super("load", "Interface/Nifty/screens/wizard/load.xml", + InitialState.Disabled); + } + // ************************************************************************* + // new methods exposed + + /** + * Determine user feedback (if any) regarding the "next screen" action. + * + * @return an empty string if ready to proceed, otherwise an explanatory + * message + */ + static String feedback() { + Model model = DacWizard.getModel(); + int numDacs = model.countDacs(); + int numSkeletonControls = model.countSControls(); + Spatial nextSpatial = model.getRootSpatial(); + String loadException = model.loadExceptionString(); + + String result; + if (!loadException.isEmpty()) { + result = loadException; + } else if (nextSpatial == null) { + result = "The model hasn't been loaded yet."; + } else if (numSkeletonControls == 0) { + result = "The model lacks a skinning/skeleton control."; + } else if (numSkeletonControls > 1) { + result = String.format( + "The model has %d skinning/skeleton controls.", + numSkeletonControls); + + } else if (model.countBones() < 1) { + if (model.findSkeleton() == null) { // new animation system + result = "The model's Armature lacks joints."; + } else { // old animation system + result = "The model's Skeleton lacks bones."; + } + + } else if (numDacs > 1) { + result = String.format("The model has %d DACs.", numDacs); + } else { + result = model.validationFeedback(); + } + + return result; + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("load"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + + DacWizard wizard = DacWizard.getApplication(); + wizard.clearScene(); + this.viewedSpatial = null; + this.viewedAnimationName = null; + this.viewedAnimationTime = Float.NaN; + + Model model = DacWizard.getModel(); + model.setShowingMeshes(true); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + updateFeedback(); + updatePath(); + updateToggleButton(); + + // Update the 3-D scene. + Model model = DacWizard.getModel(); + Spatial nextSpatial = model.getRootSpatial(); + String nextAnimationName = model.animationName(); + float nextAnimationTime = model.animationTime(); + if (nextSpatial != viewedSpatial + || !nextAnimationName.equals(viewedAnimationName) + || nextAnimationTime != viewedAnimationTime) { + DacWizard wizard = DacWizard.getApplication(); + wizard.clearScene(); + + this.viewedSpatial = nextSpatial; + this.viewedAnimationName = nextAnimationName; + this.viewedAnimationTime = nextAnimationTime; + + if (nextSpatial != null) { + Spatial cgModel = Heart.deepCopy(nextSpatial); + wizard.makeScene(cgModel, nextAnimationName, nextAnimationTime); + } + } + + updatePosingControls(); + } + // ************************************************************************* + // private methods + + /** + * Update the feedback line and the load/next buttons. + */ + private void updateFeedback() { + Model model = DacWizard.getModel(); + Spatial nextSpatial = model.getRootSpatial(); + String loadException = model.loadExceptionString(); + + String loadButton = ""; + if (loadException.isEmpty() && nextSpatial == null) { + loadButton = "Load and preview"; + } + setButtonText("load", loadButton); + + String feedback = feedback(); + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + } + + /** + * Update the posing controls. + */ + private void updatePosingControls() { + String anText = ""; + String atText = ""; + String naText = ""; + String paText = ""; + + if (viewedSpatial != null) { + Model model = DacWizard.getModel(); + int numAnimations = model.countAnimations(); + if (numAnimations > 0) { + paText = "-"; + naText = "+"; + } + + float duration = model.animationDuration(); + if (duration > 0f) { + atText = Float.toString(viewedAnimationTime) + " seconds"; + } + + anText = viewedAnimationName; + if (!anText.equals(DacWizard.bindPoseName)) { + anText = MyString.quote(anText); + } + } + + setStatusText("animationName", anText); + setButtonText("animationTime", atText); + setButtonText("nextAnimation", naText); + setButtonText("previousAnimation", paText); + } + + /** + * Update the path status and "+" buttons. + */ + private void updatePath() { + Model model = DacWizard.getModel(); + + String assetPath = model.assetPath(); + String assetRoot = model.assetRoot(); + String[] pathComponents = assetPath.split("/"); + String[] rootComponents = assetRoot.split("/"); + + String morePathButton = ""; + String moreRootButton = ""; + if (pathComponents.length > 2) { + moreRootButton = "+"; + } + if (rootComponents.length > 1) { + morePathButton = "+"; + } + + setButtonText("morePath", morePathButton); + setButtonText("moreRoot", moreRootButton); + + setStatusText("assetPath", " " + assetPath); + setStatusText("assetRoot", " " + assetRoot); + } + + /** + * Update the button to toggle skeleton visualization. + */ + private void updateToggleButton() { + String buttonText = ""; + + DacWizard app = DacWizard.getApplication(); + SkeletonVisualizer sv = app.findSkeletonVisualizer(); + Model model = DacWizard.getModel(); + Spatial root = model.getRootSpatial(); + if (sv != null && root != null) { + boolean isShown = model.isShowingSkeleton(); + sv.setEnabled(isShown); + + Skeleton skeleton = model.findSkeleton(); + String armature = (skeleton == null) ? "armature" : "skeleton"; + if (isShown) { + buttonText = "Hide " + armature; + } else { + buttonText = "Show " + armature; + } + } + + setButtonText("skeleton", buttonText); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/Model.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/Model.java index a014d718c..d14ae0185 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/Model.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/Model.java @@ -1,1405 +1,1405 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.anim.AnimClip; -import com.jme3.anim.AnimComposer; -import com.jme3.anim.Armature; -import com.jme3.anim.Joint; -import com.jme3.anim.SkinningControl; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; -import com.jme3.asset.AssetManager; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DacConfiguration; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InfluenceUtil; -import jme3utilities.MyAnimation; -import jme3utilities.MySkeleton; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.math.VectorSet; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Locators; - -/** - * The state information (MVC model) in the DacWizard application. - * - * @author Stephen Gold sgold@sonic.net - */ -class Model { - // ************************************************************************* - // constants and loggers - - /** - * magic clip/animation index used to denote bind pose - */ - final private static int bindPoseIndex = -1; - /** - * message logger for this class - */ - final static Logger logger - = Logger.getLogger(Model.class.getName()); - // ************************************************************************* - // fields - - /** - * mode for displaying angles - */ - private AngleMode angleMode = AngleMode.Degrees; - /** - * bones that influence the Mesh in any way - */ - private BitSet anyInfluenceBones; - /** - * bones that directly influence the Mesh - */ - private BitSet directInfluenceBones; - /** - * bones that will be linked - */ - private BitSet linkedBones; - /** - * whether to visualize PhysicsJoint axes in the TestScreen - */ - private boolean isShowingAxes = false; - /** - * whether to render model meshes in the TestScreen - */ - private boolean isShowingMeshes = true; - /** - * whether to visualize the skeleton/armature - */ - private boolean isShowingSkeleton = true; - /** - * PhysicsControl that will be added to the C-G model - */ - private DynamicAnimControl ragdoll; - /** - * Exception that occurred during load - */ - private Exception loadException; - /** - * animation time for the selected pose (in seconds, ≥0) - */ - private float animationTime = 0f; - /** - * task for estimating ranges of motion - */ - private FutureTask romTask; - /** - * index of the selected clip/animation, or {@link #bindPoseIndex} for bind - * pose - */ - private int animationIndex = bindPoseIndex; - /** - * index of the torso's main bone, or -1 for the default - */ - private int mainBoneIndex; - /** - * number of components in the filesystem path to the asset root - */ - private int numComponentsInRoot; - /** - * list of all clip/animation names in the loaded C-G model - */ - final private List animationNames = new ArrayList<>(32); - /** - * map manager names to sets of vertices - */ - private Map coordsMap; - /** - * callable for estimating ranges of motion - */ - private RomCallable romCallable; - /** - * root spatial of the loaded C-G model - */ - private Spatial rootSpatial; - /** - * bone/torso name of the selected PhysicsLink - */ - private String selectedLink; - /** - * components of the filesystem path to the C-G model (not null) - */ - private String[] filePathComponents = new String[0]; - // ************************************************************************* - // new methods exposed - - /** - * Return the mode for displaying angles. - * - * @return an enum value (not null) - */ - AngleMode angleMode() { - assert angleMode != null; - return angleMode; - } - - /** - * Return the duration of the selected clip/animation. - * - * @return the duration (in seconds, ≥0) - */ - float animationDuration() { - float result; - if (animationIndex == bindPoseIndex) { - result = 0f; - - } else { - result = Float.NaN; - String name = animationNames.get(animationIndex); - int skipNames = animationIndex; - - List animControls = MySpatial.listControls( - rootSpatial, AnimControl.class, null); - for (AnimControl animControl : animControls) { - Collection names = animControl.getAnimationNames(); - if (skipNames < names.size()) { - Animation animation = animControl.getAnim(name); - result = animation.getLength(); - break; - } else { - skipNames -= names.size(); - } - } - - List composers = MySpatial.listControls( - rootSpatial, AnimComposer.class, null); - for (AnimComposer composer : composers) { - Collection names = composer.getAnimClipsNames(); - if (skipNames < names.size()) { - AnimClip animClip = composer.getAnimClip(name); - result = (float) animClip.getLength(); - break; - } else { - skipNames -= names.size(); - } - } - - if (Float.isNaN(result)) { - throw new RuntimeException( - "clip/animation not found: " + MyString.quote(name)); - } - } - - assert result >= 0f : result; - return result; - } - - /** - * Return the clip/animation name for the selected pose. - * - * @return the name of the clip/animation or {@code bindPoseName} for bind - * pose (not null) - */ - String animationName() { - String result; - if (animationIndex == bindPoseIndex) { - result = DacWizard.bindPoseName; - } else { - result = animationNames.get(animationIndex); - } - - assert result != null; - return result; - } - - /** - * Return the animation time for the selected pose. - * - * @return the animation time (in seconds, ≥0) - */ - float animationTime() { - assert animationTime >= 0f : animationTime; - return animationTime; - } - - /** - * Determine the asset path to the J3O/glTF asset. The filesystem path must - * be set. - * - * @return the path (not null, not empty) - */ - String assetPath() { - int numComponents = filePathComponents.length; - if (numComponents == 0) { - throw new RuntimeException("Filesystem path not set."); - } - assert numComponentsInRoot < numComponents : numComponents; - String[] resultComponents = Arrays.copyOfRange( - filePathComponents, numComponentsInRoot, numComponents); - String result = String.join("/", resultComponents); - result = "/" + result; - - return result; - } - - /** - * Determine the filesystem path to the asset root. The filesystem path must - * be set. - * - * @return the path (not null, not empty) - */ - String assetRoot() { - int numComponents = filePathComponents.length; - if (numComponents == 0) { - throw new RuntimeException("Filesystem path not set."); - } - assert numComponentsInRoot < numComponents : numComponents; - String[] resultComponents = Arrays.copyOfRange( - filePathComponents, 0, numComponentsInRoot); - String result = String.join("/", resultComponents); - result += "/"; - - assert result != null; - assert !result.isEmpty(); - return result; - } - - /** - * Return the name of the indexed bone. A C-G model must be loaded. - * - * @param boneIndex which bone (≥0) - * @return the name (may be null) - */ - String boneName(int boneIndex) { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - String result; - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - Joint joint = armature.getJoint(boneIndex); - result = joint.getName(); - - } else { // old animation system - Bone bone = skeleton.getBone(boneIndex); - result = bone.getName(); - } - - return result; - } - - /** - * Return the configuration of the named bone/torso link. - * - * @param boneName the name of the bone/torso (not null) - * @return the pre-existing configuration (not null) - */ - LinkConfig config(String boneName) { - LinkConfig result = ragdoll.config(boneName); - return result; - } - - /** - * Copy the configured DynamicAnimControl. - * - * @return a new Control, or null if no model loaded - */ - DynamicAnimControl copyRagdoll() { - DynamicAnimControl clone = Heart.deepCopy(ragdoll); - return clone; - } - - /** - * Count how many clip/animations are in the loaded C-G model. - * - * @return the count (≥0) - */ - int countAnimations() { - int result = animationNames.size(); - return result; - } - - /** - * Count how many bones are in the skeleton. A C-G model must be loaded. - * - * @return the count (≥0) - */ - int countBones() { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - int count = 0; - Skeleton skeleton = findSkeleton(); - if (skeleton != null) { // old animation system - count = skeleton.getBoneCount(); - } else { // new animation system - Armature armature = findArmature(); - if (armature != null) { - count = armature.getJointCount(); - } - } - - assert count >= 0 : count; - return count; - } - - /** - * Count how many dynamic anim controls are in the model. - * - * @return the count (≥0) or 0 if no model loaded - */ - int countDacs() { - int count = MySpatial.countControls( - rootSpatial, DynamicAnimControl.class); - - assert count >= 0 : count; - return count; - } - - /** - * Count how many bones are managed by the specified bone/torso link. A C-G - * model must be loaded. - * - * @param managerName the bone/torso name of the manager (not null) - * @return the count (≥0) - */ - int countManagedBones(String managerName) { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - int count = 0; - - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - int numJoints = armature.getJointCount(); - for (int boneIndex = 0; boneIndex < numJoints; ++boneIndex) { - Joint joint = armature.getJoint(boneIndex); - String name = findManager(joint); - if (managerName.equals(name)) { - ++count; - } - } - - } else { // old animation system - int numBones = skeleton.getBoneCount(); - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - Bone bone = skeleton.getBone(boneIndex); - String name = findManager(bone, skeleton); - if (managerName.equals(name)) { - ++count; - } - } - } - - assert count >= 0 : count; - return count; - } - - /** - * Count how many skeleton/skinning controls are in the model. - * - * @return the count (≥0) or 0 if no model loaded - */ - int countSControls() { - int count = MySpatial.countControls(rootSpatial, SkeletonControl.class); - count += MySpatial.countControls(rootSpatial, SkinningControl.class); - - assert count >= 0 : count; - return count; - } - - /** - * Count how many tracks in the C-G model use the indexed bone. A C-G model - * must be loaded. - * - * @param boneIndex which bone (≥0) - * @return the count (≥0) - */ - int countTracks(int boneIndex) { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - int count = 0; - - List composers - = MySpatial.listControls(rootSpatial, AnimComposer.class, null); - for (AnimComposer composer : composers) { - Collection clipNames = composer.getAnimClipsNames(); - for (String clipName : clipNames) { - AnimClip clip = composer.getAnimClip(clipName); - if (MyAnimation.findTransformTrack(clip, boneIndex) != null) { - ++count; - } - } - } - - List animControls - = MySpatial.listControls(rootSpatial, AnimControl.class, null); - for (AnimControl animControl : animControls) { - Collection names = animControl.getAnimationNames(); - for (String animName : names) { - Animation animation = animControl.getAnim(animName); - if (MyAnimation.hasTrackForBone(animation, boneIndex)) { - ++count; - } - } - } - - return count; - } - - /** - * Count how many vertices would be assigned to the named bone/torso. - * - * @param boneName (not null) - * @return the count (≥0) - */ - int countVertices(String boneName) { - int count; - VectorSet vertices = coordsMap.get(boneName); - if (vertices == null) { - count = 0; - } else { - count = vertices.numVectors(); - } - - return count; - } - - /** - * Describe the influence of the indexed bone in the loaded C-G model. - * - * @param boneIndex which bone (≥0) - * @return descriptive text (not null, not empty) - */ - String describeBoneInfluence(int boneIndex) { - String result; - if (directInfluenceBones.get(boneIndex)) { - result = "has direct mesh influence"; - } else if (anyInfluenceBones.get(boneIndex)) { - result = "mesh influence (indirect only)"; - } else { - result = "NO mesh influence"; - } - - return result; - } - - /** - * Return the filesystem path to the J3O/glTF file. - * - * @return the path (not null, may be empty) - */ - String filePath() { - String result = String.join("/", filePathComponents); - assert result != null; - return result; - } - - /** - * Access the model's Skeleton, assuming it has no more than one. A C-G - * model must be loaded. - * - * @return the pre-existing instance, or null if none or multiple - */ - Skeleton findSkeleton() { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - List list = MySkeleton.listSkeletons(rootSpatial, null); - - Skeleton result = null; - if (list.size() == 1) { - result = list.get(0); - } - - return result; - } - - /** - * Access the root spatial of the loaded C-G model. - * - * @return the pre-existing Spatial, or null if no model loaded - */ - Spatial getRootSpatial() { - return rootSpatial; - } - - /** - * Test whether there's a pre-existing DynamicAnimControl with the exact - * same set of linked bones. - * - * @return true if a matching Control exists, otherwise false - */ - boolean hasConfiguredRagdoll() { - if (ragdoll == null) { - return false; - } - - int numBones = countBones(); - boolean result = true; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - String name = boneName(boneIndex); - boolean isLinked = ragdoll.hasBoneLink(name); - if (isLinked != linkedBones.get(boneIndex)) { - result = false; - break; - } - } - - return result; - } - - /** - * Test whether the indexed bone will be linked. - * - * @param boneIndex which bone (≥0) - * @return true if linked, otherwise false - */ - boolean isBoneLinked(int boneIndex) { - boolean result = linkedBones.get(boneIndex); - return result; - } - - /** - * Test whether the PhysicsJoint axes will be visualized. - * - * @return true if visualized, otherwise false - */ - boolean isShowingAxes() { - return isShowingAxes; - } - - /** - * Test whether the model meshes will be rendered. - * - * @return true if rendered, otherwise false - */ - boolean isShowingMeshes() { - return isShowingMeshes; - } - - /** - * Test whether the skeleton/armature will be visualized. - * - * @return true if visualized, otherwise false - */ - boolean isShowingSkeleton() { - return isShowingSkeleton; - } - - /** - * Determine the parent (in the link hierarchy) of the named linked bone. A - * C-G model must be loaded. - * - * @param childName the bone name of the child (not null, not empty) - * @return the bone/torso name of the parent - */ - String linkedBoneParentName(String childName) { - assert childName != null; - assert !childName.isEmpty(); - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - String name; - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - Joint child = armature.getJoint(childName); - Joint parent = child.getParent(); - if (parent == null) { // the named Joint was a root joint - name = DacConfiguration.torsoName; - } else { - name = findManager(parent); - } - - } else { // old animation system - Bone child = skeleton.getBone(childName); - Bone parent = child.getParent(); - if (parent == null) { // the named Bone was a root bone - name = DacConfiguration.torsoName; - } else { - name = findManager(parent, skeleton); - } - } - - return name; - } - - /** - * Enumerate the indices of all bones that will be linked. A C-G model must - * be loaded. - * - * @return a new array of indices (not null) - */ - int[] listLinkedBones() { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - int numBones = countBones(); - int numLinkedBones = linkedBones.cardinality(); - int[] result = new int[numLinkedBones]; - int linkedBoneIndex = 0; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - if (linkedBones.get(boneIndex)) { - result[linkedBoneIndex] = boneIndex; - ++linkedBoneIndex; - } - } - - return result; - } - - /** - * Enumerate the indices of all bones managed by the torso. A C-G model must - * be loaded. - * - * @return a new array of indices (not null) - */ - int[] listTorsoManagedBones() { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - int numManaged = countManagedBones(DacConfiguration.torsoName); - int[] result = new int[numManaged]; - - int managedIndex = 0; - int numBones = countBones(); - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - String managerName = findManager(boneIndex); - if (managerName.equals(DacConfiguration.torsoName)) { - result[managedIndex] = boneIndex; - ++managedIndex; - } - } - - return result; - } - - /** - * Attempt to load a C-G model. The filesystem path must have been - * previously set. If successful, rootSpatial, animNames, ragdoll, - * mainBoneIndex, and linkedBones are initialized. Otherwise, - * {@code rootSpatial==null} and loadException is set. - */ - void load() { - int numComponents = filePathComponents.length; - if (numComponents == 0) { - throw new RuntimeException("Filesystem path not set."); - } - - unload(); - String assetRoot = assetRoot(); - String assetPath = assetPath(); - - Locators.save(); - Locators.unregisterAll(); - Locators.registerFilesystem(assetRoot); - Locators.registerDefault(); - AssetManager assetManager = Locators.getAssetManager(); - assetManager.clearCache(); - try { - this.rootSpatial = assetManager.loadModel(assetPath); - this.loadException = null; - } catch (RuntimeException exception) { - this.rootSpatial = null; - this.loadException = exception; - } - Locators.restore(); - - animationNames.clear(); - if (rootSpatial != null) { - this.ragdoll = removeDac(); - recalculateInfluence(); - updateAnimationNames(); - - int mbIndex = -1; - if (ragdoll != null) { - String mbName = ragdoll.mainBoneName(); - mbIndex = findBoneIndex(mbName); - } - setMainBoneIndex(mbIndex); - - int numBones = countBones(); - BitSet bitset = new BitSet(numBones); // empty set - if (ragdoll != null) { - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - String name = boneName(boneIndex); - if (ragdoll.hasBoneLink(name)) { - bitset.set(boneIndex); - } - } - } - setLinkedBones(bitset); - } - } - - /** - * Return the exception that occurred during the most recent load attempt. - * - * @return the exception message, or "" if none - */ - String loadExceptionString() { - String result = ""; - if (loadException != null) { - result = loadException.toString(); - } - - return result; - } - - /** - * Return the index of the torso's main bone. - * - * @return the bone index (≥0) - */ - int mainBoneIndex() { - int result = mainBoneIndex; - if (mainBoneIndex == -1) { - List targetList = RagUtils.listDacMeshes(rootSpatial, null); - Mesh[] meshes = new Mesh[targetList.size()]; - targetList.toArray(meshes); - - Skeleton skeleton = findSkeleton(); - if (skeleton != null) { // old animation system - Bone bone = RagUtils.findMainBone(skeleton, meshes); - result = skeleton.getBoneIndex(bone); - - } else { // new animation system - Armature armature = findArmature(); - Joint armatureJoint = RagUtils.findMainJoint(armature, meshes); - result = armatureJoint.getId(); - } - } - - assert result >= 0 : result; - return result; - } - - /** - * Shift one component of the filesystem path from the asset root to the - * asset path. - */ - void morePath() { - unload(); - --numComponentsInRoot; - } - - /** - * Shift one component of the filesystem path from the asset path to the - * asset root. - */ - void moreRoot() { - unload(); - ++numComponentsInRoot; - } - - /** - * Select the next clip/animation in the loaded C-G model. - */ - void nextAnimation() { - int numAnimations = countAnimations(); - if (animationIndex < numAnimations - 1) { - ++animationIndex; - } else { - this.animationIndex = bindPoseIndex; - } - - this.animationTime = 0f; - } - - /** - * Determine the index of the parent of the indexed bone. - * - * @param boneIndex which bone (≥0) - * @return the index of the parent (≥0) or -1 for a root bone - */ - int parentIndex(int boneIndex) { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - int result; - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - Joint joint = armature.getJoint(boneIndex); - Joint parent = joint.getParent(); - - if (parent == null) { - result = -1; - } else { - result = parent.getId(); - } - - } else { // old animation system - Bone bone = skeleton.getBone(boneIndex); - Bone parent = bone.getParent(); - - if (parent == null) { - result = -1; - } else { - result = skeleton.getBoneIndex(parent); - } - } - - return result; - } - - /** - * If the range-of-motion task is done, instantiate the DynamicAnimControl - * and proceed to the "links" screen. - */ - void pollForTaskCompletion() { - if (romTask == null || !romTask.isDone()) { - return; - } - logger.log(Level.INFO, "The range-of-motion task is done."); - - RangeOfMotion[] roms; - try { - roms = romTask.get(); - } catch (ExecutionException | InterruptedException exception) { - System.out.print(exception); - return; - } - this.romCallable.cleanup(); - this.romTask = null; - - this.ragdoll = new DynamicAnimControl(); - float massParameter = 1f; - LinkConfig linkConfig = new LinkConfig( - massParameter, MassHeuristic.Density, ShapeHeuristic.VertexHull, - new Vector3f(1f, 1f, 1f), CenterHeuristic.Mean); - - ragdoll.setConfig(DacConfiguration.torsoName, linkConfig); - - int mbIndex = mainBoneIndex(); - String mbName = boneName(mbIndex); - ragdoll.setMainBoneName(mbName); - - int numBones = countBones(); - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - if (linkedBones.get(boneIndex)) { - String boneName = boneName(boneIndex); - ragdoll.link(boneName, linkConfig, roms[boneIndex]); - } - } - - selectLink(DacConfiguration.torsoName); - InputMode links = InputMode.findMode("links"); - links.setEnabled(true); - } - - /** - * Select the previous clip/animation in the loaded C-G model. - */ - void previousAnimation() { - int numAnimations = countAnimations(); - if (animationIndex == bindPoseIndex) { - this.animationIndex = numAnimations - 1; - } else { - --animationIndex; - } - - this.animationTime = 0f; - } - - /** - * Access the joint limits of the named BoneLink. - * - * @param boneName the name of the bone (not null, not empty) - * @return the pre-existing limits (not null) - */ - RangeOfMotion rom(String boneName) { - assert boneName != null; - assert !boneName.isEmpty(); - - RangeOfMotion result = ragdoll.getJointLimits(boneName); - return result; - } - - /** - * Determine which physics link is selected. - * - * @return the bone/torso name of the link, or null if no selection - */ - String selectedLink() { - return selectedLink; - } - - /** - * Select the named physics link. - * - * @param boneName the bone/torso name of the desired link (not null) - */ - void selectLink(String boneName) { - assert boneName != null; - this.selectedLink = boneName; - } - - /** - * Alter the animation time for the selected pose. - * - * @param time the desired animation time (≥0, ≤duration) - */ - void setAnimationTime(float time) { - assert time >= 0f : time; - assert time <= animationDuration() : time; - - this.animationTime = time; - } - - /** - * Reconfigure the named bone/torso. - * - * @param boneName the name of the bone, or torsoName (not null) - * @param config the desired configuration (not null) - */ - void setConfig(String boneName, LinkConfig config) { - assert boneName != null; - assert config != null; - - ragdoll.setConfig(boneName, config); - } - - /** - * Alter the model's filesystem path. - * - * @param path the desired filesystem path (not null, contains a "/") - */ - void setFilePath(String path) { - assert path != null; - assert path.contains("/"); - - this.filePathComponents = path.split("/"); - this.numComponentsInRoot = 1; - this.loadException = null; - unload(); - /* - * Use heuristics to guess how many components there are - * in the filesystem path to the asset root. - */ - int numComponents = filePathComponents.length; - assert numComponents > 0 : numComponents; - for (int componentI = 0; componentI < numComponents; ++componentI) { - String component = filePathComponents[componentI]; - switch (component) { - case "assets": - case "resources": - case "Written Assets": - if (componentI > 1) { - numComponentsInRoot = componentI - 1; - } - break; - case "Models": - if (componentI > 0 && componentI < numComponents) { - numComponentsInRoot = componentI; - } - break; - default: - } - } - } - - /** - * Alter which bones will be linked. A C-G model must be loaded. - * - * @param linkedBones the desired set of linked bones (not null) - */ - void setLinkedBones(BitSet linkedBones) { - assert linkedBones != null; - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - if (!linkedBones.equals(this.linkedBones)) { - this.linkedBones = linkedBones; - int numBones = countBones(); - String[] managerMap = new String[numBones]; - - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - for (int jointIndex = 0; jointIndex < numBones; ++jointIndex) { - Joint joint = armature.getJoint(jointIndex); - managerMap[jointIndex] = findManager(joint); - } - - } else { // old animation system - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - Bone bone = skeleton.getBone(boneIndex); - managerMap[boneIndex] = findManager(bone, skeleton); - } - } - - List targetList = RagUtils.listDacMeshes(rootSpatial, null); - Mesh[] targets = new Mesh[targetList.size()]; - targetList.toArray(targets); - - // Enumerate mesh-vertex coordinates and assign them to managers. - this.coordsMap = RagUtils.coordsMap(targets, managerMap); - } - } - - /** - * Alter the index for the main bone in the torso. - * - * @param desiredIndex the desired index (≥0) or -1 to use the default - */ - void setMainBoneIndex(int desiredIndex) { - assert desiredIndex >= -1 : desiredIndex; - this.mainBoneIndex = desiredIndex; - } - - /** - * Replace the RangeOfMotion of the named BoneLink. - * - * @param boneName the name of the bone (not null, not empty) - * @param rom the desired RangeOfMotion (not null) - */ - void setRom(String boneName, RangeOfMotion rom) { - assert boneName != null; - assert !boneName.isEmpty(); - assert rom != null; - - ragdoll.setJointLimits(boneName, rom); - } - - /** - * Alter whether model meshes will be rendered. - * - * @param setting true to render meshes, false to hide them - */ - void setShowingMeshes(boolean setting) { - this.isShowingMeshes = setting; - } - - /** - * Start a thread to estimate the range of motion for each linked bone. - */ - void startRomTask() { - this.romCallable = new RomCallable(this); - assert romTask == null; - this.romTask = new FutureTask<>(romCallable); - Thread romThread = new Thread(romTask); - romThread.start(); - } - - /** - * Toggle the mode for displaying angles. - */ - void toggleAngleMode() { - switch (angleMode) { - case Degrees: - this.angleMode = AngleMode.Radians; - break; - case Radians: - this.angleMode = AngleMode.Degrees; - break; - default: - throw new IllegalStateException("angleMode = " + angleMode); - } - } - - /** - * Toggle the visibility of PhysicsJoint axes in TestScreen. - */ - void toggleShowingAxes() { - this.isShowingAxes = !isShowingAxes; - } - - /** - * Toggle the visibility of the skeleton. - */ - void toggleShowingSkeleton() { - this.isShowingSkeleton = !isShowingSkeleton; - } - - /** - * Unload the loaded C-G model, if any. - */ - void unload() { - this.animationIndex = bindPoseIndex; - animationNames.clear(); - this.animationTime = 0f; - this.mainBoneIndex = -1; - this.rootSpatial = null; - this.ragdoll = null; - } - - /** - * Validate the loaded C-G model for use with DynamicAnimControl. - * - * @return a feedback message (not null, not empty) or an empty string if - * none - */ - String validationFeedback() { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - String result = ""; - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - try { - RagUtils.validate(rootSpatial); - RagUtils.validate(armature); - } catch (IllegalArgumentException exception) { - result = exception.getMessage(); - } - - } else { // old animation system - try { - RagUtils.validate(rootSpatial); - RagUtils.validate(skeleton); - } catch (IllegalArgumentException exception) { - result = exception.getMessage(); - } - } - - return result; - } - // ************************************************************************* - // private methods - - /** - * Access the model's Armature, assuming it doesn't have more than one - * SkinningControl. A C-G model must be loaded. - * - * @return the pre-existing instance, or null if none or multiple - */ - private Armature findArmature() { - if (rootSpatial == null) { - throw new RuntimeException("No model loaded."); - } - - List skinners = MySpatial.listControls( - rootSpatial, SkinningControl.class, null); - - Armature result = null; - if (skinners.size() == 1) { - SkinningControl control = skinners.get(0); - result = control.getArmature(); - } - - return result; - } - - /** - * Return the index of the named bone. A C-G model must be loaded. - * - * @param boneName the name of the bone to find - * @return the index (≥0) or -1 if not found - */ - private int findBoneIndex(String boneName) { - int result; - - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - result = armature.getJointIndex(boneName); - - } else { // old animation system - result = skeleton.getBoneIndex(boneName); - } - - return result; - } - - /** - * Find the manager of the indexed bone. - * - * @param startBoneIndex the index of the bone to analyze (≥0) - * @return the bone/torso name (not null) - */ - private String findManager(int startBoneIndex) { - String result; - - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - Joint startJoint = armature.getJoint(startBoneIndex); - result = findManager(startJoint); - - } else { // old animation system - Bone startBone = skeleton.getBone(startBoneIndex); - result = findManager(startBone, skeleton); - } - - return result; - } - - /** - * Find the link that manages the specified Bone. - * - * @param startBone which Bone to analyze (not null, unaffected) - * @param skeleton the Skeleton containing the Bone - * @return a bone/torso name (not null) - */ - private String findManager(Bone startBone, Skeleton skeleton) { - assert startBone != null; - - String managerName; - Bone bone = startBone; - while (true) { - int boneIndex = skeleton.getBoneIndex(bone); - if (linkedBones.get(boneIndex)) { - managerName = bone.getName(); - break; - } - bone = bone.getParent(); - if (bone == null) { - managerName = DacConfiguration.torsoName; - break; - } - } - - assert managerName != null; - return managerName; - } - - /** - * Find the link that manages the specified armature joint. - * - * @param startJoint which Joint to analyze (not null, unaffected) - * @return a bone/torso name (not null) - */ - private String findManager(Joint startJoint) { - assert startJoint != null; - - String managerName; - Joint joint = startJoint; - while (true) { - int jointIndex = joint.getId(); - if (linkedBones.get(jointIndex)) { - managerName = joint.getName(); - break; - } - joint = joint.getParent(); - if (joint == null) { - managerName = DacConfiguration.torsoName; - break; - } - } - - assert managerName != null; - return managerName; - } - - /** - * Recalculate the influence of each bone. - */ - private void recalculateInfluence() { - Skeleton skeleton = findSkeleton(); - if (skeleton == null) { // new animation system - Armature armature = findArmature(); - if (armature != null) { - this.anyInfluenceBones = InfluenceUtil.addAllInfluencers( - rootSpatial, armature); - armature.applyBindPose(); - } - } else { // old animation system - this.anyInfluenceBones = InfluenceUtil.addAllInfluencers( - rootSpatial, skeleton); - } - - int numBones = countBones(); - this.directInfluenceBones = new BitSet(numBones); - InfluenceUtil.addDirectInfluencers(rootSpatial, directInfluenceBones); - } - - /** - * Remove any DynamicAnimControl from the C-G model. - * - * @return the pre-existing control that was removed, or null if none - */ - private DynamicAnimControl removeDac() { - List list = MySpatial.listControls( - rootSpatial, DynamicAnimControl.class, null); - DynamicAnimControl result = null; - if (!list.isEmpty()) { - assert list.size() == 1 : list.size(); - result = list.get(0); - Spatial controlled = result.getSpatial(); - controlled.removeControl(result); - } - - return result; - } - - /** - * Update the list of all clips/animations in the loaded C-G model. - */ - private void updateAnimationNames() { - animationNames.clear(); - - List animControls - = MySpatial.listControls(rootSpatial, AnimControl.class, null); - for (AnimControl animControl : animControls) { - Collection names = animControl.getAnimationNames(); - animationNames.addAll(names); - } - - List composers - = MySpatial.listControls(rootSpatial, AnimComposer.class, null); - for (AnimComposer composer : composers) { - Collection names = composer.getAnimClipsNames(); - animationNames.addAll(names); - } - - this.animationIndex = bindPoseIndex; - this.animationTime = 0f; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DacConfiguration; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InfluenceUtil; +import jme3utilities.MyAnimation; +import jme3utilities.MySkeleton; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.math.VectorSet; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Locators; + +/** + * The state information (MVC model) in the DacWizard application. + * + * @author Stephen Gold sgold@sonic.net + */ +class Model { + // ************************************************************************* + // constants and loggers + + /** + * magic clip/animation index used to denote bind pose + */ + final private static int bindPoseIndex = -1; + /** + * message logger for this class + */ + final static Logger logger + = Logger.getLogger(Model.class.getName()); + // ************************************************************************* + // fields + + /** + * mode for displaying angles + */ + private AngleMode angleMode = AngleMode.Degrees; + /** + * bones that influence the Mesh in any way + */ + private BitSet anyInfluenceBones; + /** + * bones that directly influence the Mesh + */ + private BitSet directInfluenceBones; + /** + * bones that will be linked + */ + private BitSet linkedBones; + /** + * whether to visualize PhysicsJoint axes in the TestScreen + */ + private boolean isShowingAxes = false; + /** + * whether to render model meshes in the TestScreen + */ + private boolean isShowingMeshes = true; + /** + * whether to visualize the skeleton/armature + */ + private boolean isShowingSkeleton = true; + /** + * PhysicsControl that will be added to the C-G model + */ + private DynamicAnimControl ragdoll; + /** + * Exception that occurred during load + */ + private Exception loadException; + /** + * animation time for the selected pose (in seconds, ≥0) + */ + private float animationTime = 0f; + /** + * task for estimating ranges of motion + */ + private FutureTask romTask; + /** + * index of the selected clip/animation, or {@link #bindPoseIndex} for bind + * pose + */ + private int animationIndex = bindPoseIndex; + /** + * index of the torso's main bone, or -1 for the default + */ + private int mainBoneIndex; + /** + * number of components in the filesystem path to the asset root + */ + private int numComponentsInRoot; + /** + * list of all clip/animation names in the loaded C-G model + */ + final private List animationNames = new ArrayList<>(32); + /** + * map manager names to sets of vertices + */ + private Map coordsMap; + /** + * callable for estimating ranges of motion + */ + private RomCallable romCallable; + /** + * root spatial of the loaded C-G model + */ + private Spatial rootSpatial; + /** + * bone/torso name of the selected PhysicsLink + */ + private String selectedLink; + /** + * components of the filesystem path to the C-G model (not null) + */ + private String[] filePathComponents = new String[0]; + // ************************************************************************* + // new methods exposed + + /** + * Return the mode for displaying angles. + * + * @return an enum value (not null) + */ + AngleMode angleMode() { + assert angleMode != null; + return angleMode; + } + + /** + * Return the duration of the selected clip/animation. + * + * @return the duration (in seconds, ≥0) + */ + float animationDuration() { + float result; + if (animationIndex == bindPoseIndex) { + result = 0f; + + } else { + result = Float.NaN; + String name = animationNames.get(animationIndex); + int skipNames = animationIndex; + + List animControls = MySpatial.listControls( + rootSpatial, AnimControl.class, null); + for (AnimControl animControl : animControls) { + Collection names = animControl.getAnimationNames(); + if (skipNames < names.size()) { + Animation animation = animControl.getAnim(name); + result = animation.getLength(); + break; + } else { + skipNames -= names.size(); + } + } + + List composers = MySpatial.listControls( + rootSpatial, AnimComposer.class, null); + for (AnimComposer composer : composers) { + Collection names = composer.getAnimClipsNames(); + if (skipNames < names.size()) { + AnimClip animClip = composer.getAnimClip(name); + result = (float) animClip.getLength(); + break; + } else { + skipNames -= names.size(); + } + } + + if (Float.isNaN(result)) { + throw new RuntimeException( + "clip/animation not found: " + MyString.quote(name)); + } + } + + assert result >= 0f : result; + return result; + } + + /** + * Return the clip/animation name for the selected pose. + * + * @return the name of the clip/animation or {@code bindPoseName} for bind + * pose (not null) + */ + String animationName() { + String result; + if (animationIndex == bindPoseIndex) { + result = DacWizard.bindPoseName; + } else { + result = animationNames.get(animationIndex); + } + + assert result != null; + return result; + } + + /** + * Return the animation time for the selected pose. + * + * @return the animation time (in seconds, ≥0) + */ + float animationTime() { + assert animationTime >= 0f : animationTime; + return animationTime; + } + + /** + * Determine the asset path to the J3O/glTF asset. The filesystem path must + * be set. + * + * @return the path (not null, not empty) + */ + String assetPath() { + int numComponents = filePathComponents.length; + if (numComponents == 0) { + throw new RuntimeException("Filesystem path not set."); + } + assert numComponentsInRoot < numComponents : numComponents; + String[] resultComponents = Arrays.copyOfRange( + filePathComponents, numComponentsInRoot, numComponents); + String result = String.join("/", resultComponents); + result = "/" + result; + + return result; + } + + /** + * Determine the filesystem path to the asset root. The filesystem path must + * be set. + * + * @return the path (not null, not empty) + */ + String assetRoot() { + int numComponents = filePathComponents.length; + if (numComponents == 0) { + throw new RuntimeException("Filesystem path not set."); + } + assert numComponentsInRoot < numComponents : numComponents; + String[] resultComponents = Arrays.copyOfRange( + filePathComponents, 0, numComponentsInRoot); + String result = String.join("/", resultComponents); + result += "/"; + + assert result != null; + assert !result.isEmpty(); + return result; + } + + /** + * Return the name of the indexed bone. A C-G model must be loaded. + * + * @param boneIndex which bone (≥0) + * @return the name (may be null) + */ + String boneName(int boneIndex) { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + String result; + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + Joint joint = armature.getJoint(boneIndex); + result = joint.getName(); + + } else { // old animation system + Bone bone = skeleton.getBone(boneIndex); + result = bone.getName(); + } + + return result; + } + + /** + * Return the configuration of the named bone/torso link. + * + * @param boneName the name of the bone/torso (not null) + * @return the pre-existing configuration (not null) + */ + LinkConfig config(String boneName) { + LinkConfig result = ragdoll.config(boneName); + return result; + } + + /** + * Copy the configured DynamicAnimControl. + * + * @return a new Control, or null if no model loaded + */ + DynamicAnimControl copyRagdoll() { + DynamicAnimControl clone = Heart.deepCopy(ragdoll); + return clone; + } + + /** + * Count how many clip/animations are in the loaded C-G model. + * + * @return the count (≥0) + */ + int countAnimations() { + int result = animationNames.size(); + return result; + } + + /** + * Count how many bones are in the skeleton. A C-G model must be loaded. + * + * @return the count (≥0) + */ + int countBones() { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + int count = 0; + Skeleton skeleton = findSkeleton(); + if (skeleton != null) { // old animation system + count = skeleton.getBoneCount(); + } else { // new animation system + Armature armature = findArmature(); + if (armature != null) { + count = armature.getJointCount(); + } + } + + assert count >= 0 : count; + return count; + } + + /** + * Count how many dynamic anim controls are in the model. + * + * @return the count (≥0) or 0 if no model loaded + */ + int countDacs() { + int count = MySpatial.countControls( + rootSpatial, DynamicAnimControl.class); + + assert count >= 0 : count; + return count; + } + + /** + * Count how many bones are managed by the specified bone/torso link. A C-G + * model must be loaded. + * + * @param managerName the bone/torso name of the manager (not null) + * @return the count (≥0) + */ + int countManagedBones(String managerName) { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + int count = 0; + + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + int numJoints = armature.getJointCount(); + for (int boneIndex = 0; boneIndex < numJoints; ++boneIndex) { + Joint joint = armature.getJoint(boneIndex); + String name = findManager(joint); + if (managerName.equals(name)) { + ++count; + } + } + + } else { // old animation system + int numBones = skeleton.getBoneCount(); + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + Bone bone = skeleton.getBone(boneIndex); + String name = findManager(bone, skeleton); + if (managerName.equals(name)) { + ++count; + } + } + } + + assert count >= 0 : count; + return count; + } + + /** + * Count how many skeleton/skinning controls are in the model. + * + * @return the count (≥0) or 0 if no model loaded + */ + int countSControls() { + int count = MySpatial.countControls(rootSpatial, SkeletonControl.class); + count += MySpatial.countControls(rootSpatial, SkinningControl.class); + + assert count >= 0 : count; + return count; + } + + /** + * Count how many tracks in the C-G model use the indexed bone. A C-G model + * must be loaded. + * + * @param boneIndex which bone (≥0) + * @return the count (≥0) + */ + int countTracks(int boneIndex) { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + int count = 0; + + List composers + = MySpatial.listControls(rootSpatial, AnimComposer.class, null); + for (AnimComposer composer : composers) { + Collection clipNames = composer.getAnimClipsNames(); + for (String clipName : clipNames) { + AnimClip clip = composer.getAnimClip(clipName); + if (MyAnimation.findTransformTrack(clip, boneIndex) != null) { + ++count; + } + } + } + + List animControls + = MySpatial.listControls(rootSpatial, AnimControl.class, null); + for (AnimControl animControl : animControls) { + Collection names = animControl.getAnimationNames(); + for (String animName : names) { + Animation animation = animControl.getAnim(animName); + if (MyAnimation.hasTrackForBone(animation, boneIndex)) { + ++count; + } + } + } + + return count; + } + + /** + * Count how many vertices would be assigned to the named bone/torso. + * + * @param boneName (not null) + * @return the count (≥0) + */ + int countVertices(String boneName) { + int count; + VectorSet vertices = coordsMap.get(boneName); + if (vertices == null) { + count = 0; + } else { + count = vertices.numVectors(); + } + + return count; + } + + /** + * Describe the influence of the indexed bone in the loaded C-G model. + * + * @param boneIndex which bone (≥0) + * @return descriptive text (not null, not empty) + */ + String describeBoneInfluence(int boneIndex) { + String result; + if (directInfluenceBones.get(boneIndex)) { + result = "has direct mesh influence"; + } else if (anyInfluenceBones.get(boneIndex)) { + result = "mesh influence (indirect only)"; + } else { + result = "NO mesh influence"; + } + + return result; + } + + /** + * Return the filesystem path to the J3O/glTF file. + * + * @return the path (not null, may be empty) + */ + String filePath() { + String result = String.join("/", filePathComponents); + assert result != null; + return result; + } + + /** + * Access the model's Skeleton, assuming it has no more than one. A C-G + * model must be loaded. + * + * @return the pre-existing instance, or null if none or multiple + */ + Skeleton findSkeleton() { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + List list = MySkeleton.listSkeletons(rootSpatial, null); + + Skeleton result = null; + if (list.size() == 1) { + result = list.get(0); + } + + return result; + } + + /** + * Access the root spatial of the loaded C-G model. + * + * @return the pre-existing Spatial, or null if no model loaded + */ + Spatial getRootSpatial() { + return rootSpatial; + } + + /** + * Test whether there's a pre-existing DynamicAnimControl with the exact + * same set of linked bones. + * + * @return true if a matching Control exists, otherwise false + */ + boolean hasConfiguredRagdoll() { + if (ragdoll == null) { + return false; + } + + int numBones = countBones(); + boolean result = true; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + String name = boneName(boneIndex); + boolean isLinked = ragdoll.hasBoneLink(name); + if (isLinked != linkedBones.get(boneIndex)) { + result = false; + break; + } + } + + return result; + } + + /** + * Test whether the indexed bone will be linked. + * + * @param boneIndex which bone (≥0) + * @return true if linked, otherwise false + */ + boolean isBoneLinked(int boneIndex) { + boolean result = linkedBones.get(boneIndex); + return result; + } + + /** + * Test whether the PhysicsJoint axes will be visualized. + * + * @return true if visualized, otherwise false + */ + boolean isShowingAxes() { + return isShowingAxes; + } + + /** + * Test whether the model meshes will be rendered. + * + * @return true if rendered, otherwise false + */ + boolean isShowingMeshes() { + return isShowingMeshes; + } + + /** + * Test whether the skeleton/armature will be visualized. + * + * @return true if visualized, otherwise false + */ + boolean isShowingSkeleton() { + return isShowingSkeleton; + } + + /** + * Determine the parent (in the link hierarchy) of the named linked bone. A + * C-G model must be loaded. + * + * @param childName the bone name of the child (not null, not empty) + * @return the bone/torso name of the parent + */ + String linkedBoneParentName(String childName) { + assert childName != null; + assert !childName.isEmpty(); + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + String name; + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + Joint child = armature.getJoint(childName); + Joint parent = child.getParent(); + if (parent == null) { // the named Joint was a root joint + name = DacConfiguration.torsoName; + } else { + name = findManager(parent); + } + + } else { // old animation system + Bone child = skeleton.getBone(childName); + Bone parent = child.getParent(); + if (parent == null) { // the named Bone was a root bone + name = DacConfiguration.torsoName; + } else { + name = findManager(parent, skeleton); + } + } + + return name; + } + + /** + * Enumerate the indices of all bones that will be linked. A C-G model must + * be loaded. + * + * @return a new array of indices (not null) + */ + int[] listLinkedBones() { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + int numBones = countBones(); + int numLinkedBones = linkedBones.cardinality(); + int[] result = new int[numLinkedBones]; + int linkedBoneIndex = 0; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + if (linkedBones.get(boneIndex)) { + result[linkedBoneIndex] = boneIndex; + ++linkedBoneIndex; + } + } + + return result; + } + + /** + * Enumerate the indices of all bones managed by the torso. A C-G model must + * be loaded. + * + * @return a new array of indices (not null) + */ + int[] listTorsoManagedBones() { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + int numManaged = countManagedBones(DacConfiguration.torsoName); + int[] result = new int[numManaged]; + + int managedIndex = 0; + int numBones = countBones(); + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + String managerName = findManager(boneIndex); + if (managerName.equals(DacConfiguration.torsoName)) { + result[managedIndex] = boneIndex; + ++managedIndex; + } + } + + return result; + } + + /** + * Attempt to load a C-G model. The filesystem path must have been + * previously set. If successful, rootSpatial, animNames, ragdoll, + * mainBoneIndex, and linkedBones are initialized. Otherwise, + * {@code rootSpatial==null} and loadException is set. + */ + void load() { + int numComponents = filePathComponents.length; + if (numComponents == 0) { + throw new RuntimeException("Filesystem path not set."); + } + + unload(); + String assetRoot = assetRoot(); + String assetPath = assetPath(); + + Locators.save(); + Locators.unregisterAll(); + Locators.registerFilesystem(assetRoot); + Locators.registerDefault(); + AssetManager assetManager = Locators.getAssetManager(); + assetManager.clearCache(); + try { + this.rootSpatial = assetManager.loadModel(assetPath); + this.loadException = null; + } catch (RuntimeException exception) { + this.rootSpatial = null; + this.loadException = exception; + } + Locators.restore(); + + animationNames.clear(); + if (rootSpatial != null) { + this.ragdoll = removeDac(); + recalculateInfluence(); + updateAnimationNames(); + + int mbIndex = -1; + if (ragdoll != null) { + String mbName = ragdoll.mainBoneName(); + mbIndex = findBoneIndex(mbName); + } + setMainBoneIndex(mbIndex); + + int numBones = countBones(); + BitSet bitset = new BitSet(numBones); // empty set + if (ragdoll != null) { + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + String name = boneName(boneIndex); + if (ragdoll.hasBoneLink(name)) { + bitset.set(boneIndex); + } + } + } + setLinkedBones(bitset); + } + } + + /** + * Return the exception that occurred during the most recent load attempt. + * + * @return the exception message, or "" if none + */ + String loadExceptionString() { + String result = ""; + if (loadException != null) { + result = loadException.toString(); + } + + return result; + } + + /** + * Return the index of the torso's main bone. + * + * @return the bone index (≥0) + */ + int mainBoneIndex() { + int result = mainBoneIndex; + if (mainBoneIndex == -1) { + List targetList = RagUtils.listDacMeshes(rootSpatial, null); + Mesh[] meshes = new Mesh[targetList.size()]; + targetList.toArray(meshes); + + Skeleton skeleton = findSkeleton(); + if (skeleton != null) { // old animation system + Bone bone = RagUtils.findMainBone(skeleton, meshes); + result = skeleton.getBoneIndex(bone); + + } else { // new animation system + Armature armature = findArmature(); + Joint armatureJoint = RagUtils.findMainJoint(armature, meshes); + result = armatureJoint.getId(); + } + } + + assert result >= 0 : result; + return result; + } + + /** + * Shift one component of the filesystem path from the asset root to the + * asset path. + */ + void morePath() { + unload(); + --numComponentsInRoot; + } + + /** + * Shift one component of the filesystem path from the asset path to the + * asset root. + */ + void moreRoot() { + unload(); + ++numComponentsInRoot; + } + + /** + * Select the next clip/animation in the loaded C-G model. + */ + void nextAnimation() { + int numAnimations = countAnimations(); + if (animationIndex < numAnimations - 1) { + ++animationIndex; + } else { + this.animationIndex = bindPoseIndex; + } + + this.animationTime = 0f; + } + + /** + * Determine the index of the parent of the indexed bone. + * + * @param boneIndex which bone (≥0) + * @return the index of the parent (≥0) or -1 for a root bone + */ + int parentIndex(int boneIndex) { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + int result; + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + Joint joint = armature.getJoint(boneIndex); + Joint parent = joint.getParent(); + + if (parent == null) { + result = -1; + } else { + result = parent.getId(); + } + + } else { // old animation system + Bone bone = skeleton.getBone(boneIndex); + Bone parent = bone.getParent(); + + if (parent == null) { + result = -1; + } else { + result = skeleton.getBoneIndex(parent); + } + } + + return result; + } + + /** + * If the range-of-motion task is done, instantiate the DynamicAnimControl + * and proceed to the "links" screen. + */ + void pollForTaskCompletion() { + if (romTask == null || !romTask.isDone()) { + return; + } + logger.log(Level.INFO, "The range-of-motion task is done."); + + RangeOfMotion[] roms; + try { + roms = romTask.get(); + } catch (ExecutionException | InterruptedException exception) { + System.out.print(exception); + return; + } + this.romCallable.cleanup(); + this.romTask = null; + + this.ragdoll = new DynamicAnimControl(); + float massParameter = 1f; + LinkConfig linkConfig = new LinkConfig( + massParameter, MassHeuristic.Density, ShapeHeuristic.VertexHull, + new Vector3f(1f, 1f, 1f), CenterHeuristic.Mean); + + ragdoll.setConfig(DacConfiguration.torsoName, linkConfig); + + int mbIndex = mainBoneIndex(); + String mbName = boneName(mbIndex); + ragdoll.setMainBoneName(mbName); + + int numBones = countBones(); + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + if (linkedBones.get(boneIndex)) { + String boneName = boneName(boneIndex); + ragdoll.link(boneName, linkConfig, roms[boneIndex]); + } + } + + selectLink(DacConfiguration.torsoName); + InputMode links = InputMode.findMode("links"); + links.setEnabled(true); + } + + /** + * Select the previous clip/animation in the loaded C-G model. + */ + void previousAnimation() { + int numAnimations = countAnimations(); + if (animationIndex == bindPoseIndex) { + this.animationIndex = numAnimations - 1; + } else { + --animationIndex; + } + + this.animationTime = 0f; + } + + /** + * Access the joint limits of the named BoneLink. + * + * @param boneName the name of the bone (not null, not empty) + * @return the pre-existing limits (not null) + */ + RangeOfMotion rom(String boneName) { + assert boneName != null; + assert !boneName.isEmpty(); + + RangeOfMotion result = ragdoll.getJointLimits(boneName); + return result; + } + + /** + * Determine which physics link is selected. + * + * @return the bone/torso name of the link, or null if no selection + */ + String selectedLink() { + return selectedLink; + } + + /** + * Select the named physics link. + * + * @param boneName the bone/torso name of the desired link (not null) + */ + void selectLink(String boneName) { + assert boneName != null; + this.selectedLink = boneName; + } + + /** + * Alter the animation time for the selected pose. + * + * @param time the desired animation time (≥0, ≤duration) + */ + void setAnimationTime(float time) { + assert time >= 0f : time; + assert time <= animationDuration() : time; + + this.animationTime = time; + } + + /** + * Reconfigure the named bone/torso. + * + * @param boneName the name of the bone, or torsoName (not null) + * @param config the desired configuration (not null) + */ + void setConfig(String boneName, LinkConfig config) { + assert boneName != null; + assert config != null; + + ragdoll.setConfig(boneName, config); + } + + /** + * Alter the model's filesystem path. + * + * @param path the desired filesystem path (not null, contains a "/") + */ + void setFilePath(String path) { + assert path != null; + assert path.contains("/"); + + this.filePathComponents = path.split("/"); + this.numComponentsInRoot = 1; + this.loadException = null; + unload(); + /* + * Use heuristics to guess how many components there are + * in the filesystem path to the asset root. + */ + int numComponents = filePathComponents.length; + assert numComponents > 0 : numComponents; + for (int componentI = 0; componentI < numComponents; ++componentI) { + String component = filePathComponents[componentI]; + switch (component) { + case "assets": + case "resources": + case "Written Assets": + if (componentI > 1) { + numComponentsInRoot = componentI - 1; + } + break; + case "Models": + if (componentI > 0 && componentI < numComponents) { + numComponentsInRoot = componentI; + } + break; + default: + } + } + } + + /** + * Alter which bones will be linked. A C-G model must be loaded. + * + * @param linkedBones the desired set of linked bones (not null) + */ + void setLinkedBones(BitSet linkedBones) { + assert linkedBones != null; + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + if (!linkedBones.equals(this.linkedBones)) { + this.linkedBones = linkedBones; + int numBones = countBones(); + String[] managerMap = new String[numBones]; + + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + for (int jointIndex = 0; jointIndex < numBones; ++jointIndex) { + Joint joint = armature.getJoint(jointIndex); + managerMap[jointIndex] = findManager(joint); + } + + } else { // old animation system + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + Bone bone = skeleton.getBone(boneIndex); + managerMap[boneIndex] = findManager(bone, skeleton); + } + } + + List targetList = RagUtils.listDacMeshes(rootSpatial, null); + Mesh[] targets = new Mesh[targetList.size()]; + targetList.toArray(targets); + + // Enumerate mesh-vertex coordinates and assign them to managers. + this.coordsMap = RagUtils.coordsMap(targets, managerMap); + } + } + + /** + * Alter the index for the main bone in the torso. + * + * @param desiredIndex the desired index (≥0) or -1 to use the default + */ + void setMainBoneIndex(int desiredIndex) { + assert desiredIndex >= -1 : desiredIndex; + this.mainBoneIndex = desiredIndex; + } + + /** + * Replace the RangeOfMotion of the named BoneLink. + * + * @param boneName the name of the bone (not null, not empty) + * @param rom the desired RangeOfMotion (not null) + */ + void setRom(String boneName, RangeOfMotion rom) { + assert boneName != null; + assert !boneName.isEmpty(); + assert rom != null; + + ragdoll.setJointLimits(boneName, rom); + } + + /** + * Alter whether model meshes will be rendered. + * + * @param setting true to render meshes, false to hide them + */ + void setShowingMeshes(boolean setting) { + this.isShowingMeshes = setting; + } + + /** + * Start a thread to estimate the range of motion for each linked bone. + */ + void startRomTask() { + this.romCallable = new RomCallable(this); + assert romTask == null; + this.romTask = new FutureTask<>(romCallable); + Thread romThread = new Thread(romTask); + romThread.start(); + } + + /** + * Toggle the mode for displaying angles. + */ + void toggleAngleMode() { + switch (angleMode) { + case Degrees: + this.angleMode = AngleMode.Radians; + break; + case Radians: + this.angleMode = AngleMode.Degrees; + break; + default: + throw new IllegalStateException("angleMode = " + angleMode); + } + } + + /** + * Toggle the visibility of PhysicsJoint axes in TestScreen. + */ + void toggleShowingAxes() { + this.isShowingAxes = !isShowingAxes; + } + + /** + * Toggle the visibility of the skeleton. + */ + void toggleShowingSkeleton() { + this.isShowingSkeleton = !isShowingSkeleton; + } + + /** + * Unload the loaded C-G model, if any. + */ + void unload() { + this.animationIndex = bindPoseIndex; + animationNames.clear(); + this.animationTime = 0f; + this.mainBoneIndex = -1; + this.rootSpatial = null; + this.ragdoll = null; + } + + /** + * Validate the loaded C-G model for use with DynamicAnimControl. + * + * @return a feedback message (not null, not empty) or an empty string if + * none + */ + String validationFeedback() { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + String result = ""; + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + try { + RagUtils.validate(rootSpatial); + RagUtils.validate(armature); + } catch (IllegalArgumentException exception) { + result = exception.getMessage(); + } + + } else { // old animation system + try { + RagUtils.validate(rootSpatial); + RagUtils.validate(skeleton); + } catch (IllegalArgumentException exception) { + result = exception.getMessage(); + } + } + + return result; + } + // ************************************************************************* + // private methods + + /** + * Access the model's Armature, assuming it doesn't have more than one + * SkinningControl. A C-G model must be loaded. + * + * @return the pre-existing instance, or null if none or multiple + */ + private Armature findArmature() { + if (rootSpatial == null) { + throw new RuntimeException("No model loaded."); + } + + List skinners = MySpatial.listControls( + rootSpatial, SkinningControl.class, null); + + Armature result = null; + if (skinners.size() == 1) { + SkinningControl control = skinners.get(0); + result = control.getArmature(); + } + + return result; + } + + /** + * Return the index of the named bone. A C-G model must be loaded. + * + * @param boneName the name of the bone to find + * @return the index (≥0) or -1 if not found + */ + private int findBoneIndex(String boneName) { + int result; + + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + result = armature.getJointIndex(boneName); + + } else { // old animation system + result = skeleton.getBoneIndex(boneName); + } + + return result; + } + + /** + * Find the manager of the indexed bone. + * + * @param startBoneIndex the index of the bone to analyze (≥0) + * @return the bone/torso name (not null) + */ + private String findManager(int startBoneIndex) { + String result; + + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + Joint startJoint = armature.getJoint(startBoneIndex); + result = findManager(startJoint); + + } else { // old animation system + Bone startBone = skeleton.getBone(startBoneIndex); + result = findManager(startBone, skeleton); + } + + return result; + } + + /** + * Find the link that manages the specified Bone. + * + * @param startBone which Bone to analyze (not null, unaffected) + * @param skeleton the Skeleton containing the Bone + * @return a bone/torso name (not null) + */ + private String findManager(Bone startBone, Skeleton skeleton) { + assert startBone != null; + + String managerName; + Bone bone = startBone; + while (true) { + int boneIndex = skeleton.getBoneIndex(bone); + if (linkedBones.get(boneIndex)) { + managerName = bone.getName(); + break; + } + bone = bone.getParent(); + if (bone == null) { + managerName = DacConfiguration.torsoName; + break; + } + } + + assert managerName != null; + return managerName; + } + + /** + * Find the link that manages the specified armature joint. + * + * @param startJoint which Joint to analyze (not null, unaffected) + * @return a bone/torso name (not null) + */ + private String findManager(Joint startJoint) { + assert startJoint != null; + + String managerName; + Joint joint = startJoint; + while (true) { + int jointIndex = joint.getId(); + if (linkedBones.get(jointIndex)) { + managerName = joint.getName(); + break; + } + joint = joint.getParent(); + if (joint == null) { + managerName = DacConfiguration.torsoName; + break; + } + } + + assert managerName != null; + return managerName; + } + + /** + * Recalculate the influence of each bone. + */ + private void recalculateInfluence() { + Skeleton skeleton = findSkeleton(); + if (skeleton == null) { // new animation system + Armature armature = findArmature(); + if (armature != null) { + this.anyInfluenceBones = InfluenceUtil.addAllInfluencers( + rootSpatial, armature); + armature.applyBindPose(); + } + } else { // old animation system + this.anyInfluenceBones = InfluenceUtil.addAllInfluencers( + rootSpatial, skeleton); + } + + int numBones = countBones(); + this.directInfluenceBones = new BitSet(numBones); + InfluenceUtil.addDirectInfluencers(rootSpatial, directInfluenceBones); + } + + /** + * Remove any DynamicAnimControl from the C-G model. + * + * @return the pre-existing control that was removed, or null if none + */ + private DynamicAnimControl removeDac() { + List list = MySpatial.listControls( + rootSpatial, DynamicAnimControl.class, null); + DynamicAnimControl result = null; + if (!list.isEmpty()) { + assert list.size() == 1 : list.size(); + result = list.get(0); + Spatial controlled = result.getSpatial(); + controlled.removeControl(result); + } + + return result; + } + + /** + * Update the list of all clips/animations in the loaded C-G model. + */ + private void updateAnimationNames() { + animationNames.clear(); + + List animControls + = MySpatial.listControls(rootSpatial, AnimControl.class, null); + for (AnimControl animControl : animControls) { + Collection names = animControl.getAnimationNames(); + animationNames.addAll(names); + } + + List composers + = MySpatial.listControls(rootSpatial, AnimComposer.class, null); + for (AnimComposer composer : composers) { + Collection names = composer.getAnimClipsNames(); + animationNames.addAll(names); + } + + this.animationIndex = bindPoseIndex; + this.animationTime = 0f; + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/RomCallable.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/RomCallable.java index 7b72f1dad..7fd96917f 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/RomCallable.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/RomCallable.java @@ -1,369 +1,369 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.anim.AnimClip; -import com.jme3.anim.AnimComposer; -import com.jme3.anim.Armature; -import com.jme3.anim.Joint; -import com.jme3.anim.SkinningControl; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.AbstractControl; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MySpatial; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.noise.Generator; -import jme3utilities.wes.Pose; -import jme3utilities.wes.TweenTransforms; - -/** - * A Callable for asynchronously estimating the ranges of motion of the loaded - * C-G model, based on a pseudo-random sample its animations. - * - * @author Stephen Gold sgold@sonic.net - */ -class RomCallable implements Callable, PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger - = Logger.getLogger(RomCallable.class.getName()); - // ************************************************************************* - // fields - - /** - * initial state of physics debug visualization - */ - final private boolean wasDebugEnabled; - /** - * temporary DynamicAnimationControl for use with the temporary model - */ - final private DynamicAnimControl tempDac; - /** - * pseudo-random generator for skeleton poses - */ - final private Generator generator = new Generator(); - /** - * information about the subject C-G model - */ - final private Model model; - /** - * root of the temporary C-G model - */ - final private Spatial tempModelRoot; - /** - * interpolation techniques used when calculating skeleton poses - */ - final private TweenTransforms techniques = new TweenTransforms(); - /** - * maximum angles seen for each rotational axis of each physics joint - */ - final private Vector3f[] maxima; - /** - * minimum angles seen for each rotational axis of each physics joint - */ - final private Vector3f[] minima; - // ************************************************************************* - // constructors - - /** - * Instantiate a callable to analyze the specified model. - * - * @param subjectModel the model to analyze (not null) - */ - RomCallable(Model subjectModel) { - this.model = subjectModel; - - // Initialize accumulators for maximum and minimum rotation angles. - int numBones = model.countBones(); - this.maxima = new Vector3f[numBones]; - this.minima = new Vector3f[numBones]; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - if (model.isBoneLinked(boneIndex)) { - maxima[boneIndex] = new Vector3f(0f, 0f, 0f); - minima[boneIndex] = new Vector3f(0f, 0f, 0f); - } - } - - // Temporarily enable physics-debug visualization. - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - this.wasDebugEnabled = bulletAppState.isDebugEnabled(); - if (!wasDebugEnabled) { - bulletAppState.setDebugEnabled(true); - } - /* - * Create a temporary copy of the C-G model and attach it - * to the scene graph. - */ - Spatial cgModel = model.getRootSpatial(); - - DacWizard wizard = DacWizard.getApplication(); - wizard.clearScene(); - - this.tempModelRoot = Heart.deepCopy(cgModel); - String animationName = model.animationName(); - float animationTime = model.animationTime(); - wizard.makeScene(tempModelRoot, animationName, animationTime); - /* - * Add a DynamicAnimControl to the copy. Since the control will - * stay in kinematic mode, its masses and ranges of motion - * don't matter. - */ - float mass = 1f; - RangeOfMotion stiffRom = new RangeOfMotion(); - AbstractControl sControl = RagUtils.findSControl(tempModelRoot); - if (sControl instanceof SkinningControl) { // new animation system - Armature armature = ((SkinningControl) sControl).getArmature(); - this.tempDac = new DynamicAnimControl() { - @Override - public void update(float tpf) { - applyRandomPose(); - super.update(tpf); - } - }; - for (int jointIndex = 0; jointIndex < numBones; ++jointIndex) { - if (model.isBoneLinked(jointIndex)) { - Joint joint = armature.getJoint(jointIndex); - String boneName = joint.getName(); - tempDac.link(boneName, mass, stiffRom); - } - } - - } else { // old animation system - Skeleton skeleton = ((SkeletonControl) sControl).getSkeleton(); - this.tempDac = new DynamicAnimControl() { - @Override - public void update(float tpf) { - applyRandomPose(); - super.update(tpf); - } - }; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - if (model.isBoneLinked(boneIndex)) { - Bone bone = skeleton.getBone(boneIndex); - String boneName = bone.getName(); - tempDac.link(boneName, mass, stiffRom); - } - } - } - - int mbIndex = model.mainBoneIndex(); - String mbName = model.boneName(mbIndex); - tempDac.setMainBoneName(mbName); - - Spatial controlledSpatial = sControl.getSpatial(); - controlledSpatial.addControl(tempDac); - - // Disable contact response for all rigid bodies in the ragdoll. - PhysicsRigidBody[] bodies = tempDac.listRigidBodies(); - for (PhysicsRigidBody body : bodies) { - body.setContactResponse(false); - } - - // Add the ragdoll to physics space. - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - assert physicsSpace.isEmpty(); - tempDac.setPhysicsSpace(physicsSpace); - physicsSpace.addTickListener(this); - } - // ************************************************************************* - // new methods exposed - - /** - * Clean up the scene graph and the physics. - */ - void cleanup() { - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - tempModelRoot.removeFromParent(); - physicsSpace.removeTickListener(this); - tempDac.setPhysicsSpace(null); - assert physicsSpace.isEmpty(); - - if (!wasDebugEnabled) { - bulletAppState.setDebugEnabled(false); - } - } - // ************************************************************************* - // Callable methods - - /** - * Estimate the range of motion of each linked bone in the loaded C-G model. - * - * @return a new map from bone indices to ranges of motion - */ - @Override - public RangeOfMotion[] call() throws InterruptedException { - // Accumulate joint-angle statistics for 10 seconds. - Thread.sleep(10_000); - - // Convert the statistics into ranges of motion. - int numBones = model.countBones(); - RangeOfMotion[] roms = new RangeOfMotion[numBones]; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - if (model.isBoneLinked(boneIndex)) { - Vector3f max = maxima[boneIndex]; - Vector3f min = minima[boneIndex]; - roms[boneIndex] = new RangeOfMotion( - max.x, min.x, max.y, min.y, max.z, min.z); - } - } - - return roms; - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just after the simulation has been stepped. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // Read joint angles from the ragdoll and update statistics. - Vector3f angles = new Vector3f(); - int numBones = model.countBones(); - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - String boneName = model.boneName(boneIndex); - BoneLink link = tempDac.findBoneLink(boneName); - if (link != null) { - PhysicsJoint joint = link.getJoint(); - SixDofJoint sixDof = (SixDofJoint) joint; - sixDof.getAngles(angles); - assert angles.x >= -FastMath.PI : angles; - assert angles.x <= FastMath.PI : angles; - assert angles.y >= -FastMath.PI : angles; - assert angles.y <= FastMath.PI : angles; - assert angles.z >= -FastMath.PI : angles; - assert angles.z <= FastMath.PI : angles; - MyVector3f.accumulateMaxima(maxima[boneIndex], angles); - MyVector3f.accumulateMinima(minima[boneIndex], angles); - } - } - } - - /** - * Callback from Bullet, invoked just before the simulation is stepped. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Apply a pseudo-random Pose to the Armature or Skeleton of the temporary - * C-G model. - */ - @SuppressWarnings("unchecked") - private void applyRandomPose() { - // Choose an AnimComposer or AnimControl. - List controls = MySpatial.listControls( - tempModelRoot, AnimControl.class, null); - MySpatial.listControls(tempModelRoot, AnimComposer.class, controls); - AbstractControl control = (AbstractControl) generator.pick(controls); - - if (control instanceof AnimControl) { - AnimControl animControl = (AnimControl) control; - - // Choose an Animation. - Collection nameCollection = animControl.getAnimationNames(); - int numAnimations = nameCollection.size(); - String[] nameArray = new String[numAnimations]; - nameCollection.toArray(nameArray); - String animationName = generator.pick(nameArray); - if (animationName == null) { - return; - } - Animation animation = animControl.getAnim(animationName); - - // Choose an animation time. - float duration = animation.getLength(); - float animationTime = generator.nextFloat(0f, duration); - - Skeleton skeleton = tempDac.getSkeleton(); - Pose pose = new Pose(skeleton); - pose.setToAnimation(animation, animationTime, techniques); - pose.applyTo(skeleton); - - } else if (control instanceof AnimComposer) { - AnimComposer composer = (AnimComposer) control; - - // Choose an AnimClip. - Collection clips = composer.getAnimClips(); - int numClips = clips.size(); - AnimClip[] clipArray = new AnimClip[numClips]; - clips.toArray(clipArray); - AnimClip clip = generator.pick(clipArray); - if (clip == null) { - return; - } - - // Choose an animation time. - double duration = clip.getLength(); - double animationTime = duration * generator.nextDouble(); - - Armature armature = tempDac.getArmature(); - Pose pose = new Pose(armature); - pose.setToClip(clip, animationTime); - pose.applyTo(armature); - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MySpatial; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.noise.Generator; +import jme3utilities.wes.Pose; +import jme3utilities.wes.TweenTransforms; + +/** + * A Callable for asynchronously estimating the ranges of motion of the loaded + * C-G model, based on a pseudo-random sample its animations. + * + * @author Stephen Gold sgold@sonic.net + */ +class RomCallable implements Callable, PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger + = Logger.getLogger(RomCallable.class.getName()); + // ************************************************************************* + // fields + + /** + * initial state of physics debug visualization + */ + final private boolean wasDebugEnabled; + /** + * temporary DynamicAnimationControl for use with the temporary model + */ + final private DynamicAnimControl tempDac; + /** + * pseudo-random generator for skeleton poses + */ + final private Generator generator = new Generator(); + /** + * information about the subject C-G model + */ + final private Model model; + /** + * root of the temporary C-G model + */ + final private Spatial tempModelRoot; + /** + * interpolation techniques used when calculating skeleton poses + */ + final private TweenTransforms techniques = new TweenTransforms(); + /** + * maximum angles seen for each rotational axis of each physics joint + */ + final private Vector3f[] maxima; + /** + * minimum angles seen for each rotational axis of each physics joint + */ + final private Vector3f[] minima; + // ************************************************************************* + // constructors + + /** + * Instantiate a callable to analyze the specified model. + * + * @param subjectModel the model to analyze (not null) + */ + RomCallable(Model subjectModel) { + this.model = subjectModel; + + // Initialize accumulators for maximum and minimum rotation angles. + int numBones = model.countBones(); + this.maxima = new Vector3f[numBones]; + this.minima = new Vector3f[numBones]; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + if (model.isBoneLinked(boneIndex)) { + maxima[boneIndex] = new Vector3f(0f, 0f, 0f); + minima[boneIndex] = new Vector3f(0f, 0f, 0f); + } + } + + // Temporarily enable physics-debug visualization. + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + this.wasDebugEnabled = bulletAppState.isDebugEnabled(); + if (!wasDebugEnabled) { + bulletAppState.setDebugEnabled(true); + } + /* + * Create a temporary copy of the C-G model and attach it + * to the scene graph. + */ + Spatial cgModel = model.getRootSpatial(); + + DacWizard wizard = DacWizard.getApplication(); + wizard.clearScene(); + + this.tempModelRoot = Heart.deepCopy(cgModel); + String animationName = model.animationName(); + float animationTime = model.animationTime(); + wizard.makeScene(tempModelRoot, animationName, animationTime); + /* + * Add a DynamicAnimControl to the copy. Since the control will + * stay in kinematic mode, its masses and ranges of motion + * don't matter. + */ + float mass = 1f; + RangeOfMotion stiffRom = new RangeOfMotion(); + AbstractControl sControl = RagUtils.findSControl(tempModelRoot); + if (sControl instanceof SkinningControl) { // new animation system + Armature armature = ((SkinningControl) sControl).getArmature(); + this.tempDac = new DynamicAnimControl() { + @Override + public void update(float tpf) { + applyRandomPose(); + super.update(tpf); + } + }; + for (int jointIndex = 0; jointIndex < numBones; ++jointIndex) { + if (model.isBoneLinked(jointIndex)) { + Joint joint = armature.getJoint(jointIndex); + String boneName = joint.getName(); + tempDac.link(boneName, mass, stiffRom); + } + } + + } else { // old animation system + Skeleton skeleton = ((SkeletonControl) sControl).getSkeleton(); + this.tempDac = new DynamicAnimControl() { + @Override + public void update(float tpf) { + applyRandomPose(); + super.update(tpf); + } + }; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + if (model.isBoneLinked(boneIndex)) { + Bone bone = skeleton.getBone(boneIndex); + String boneName = bone.getName(); + tempDac.link(boneName, mass, stiffRom); + } + } + } + + int mbIndex = model.mainBoneIndex(); + String mbName = model.boneName(mbIndex); + tempDac.setMainBoneName(mbName); + + Spatial controlledSpatial = sControl.getSpatial(); + controlledSpatial.addControl(tempDac); + + // Disable contact response for all rigid bodies in the ragdoll. + PhysicsRigidBody[] bodies = tempDac.listRigidBodies(); + for (PhysicsRigidBody body : bodies) { + body.setContactResponse(false); + } + + // Add the ragdoll to physics space. + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + assert physicsSpace.isEmpty(); + tempDac.setPhysicsSpace(physicsSpace); + physicsSpace.addTickListener(this); + } + // ************************************************************************* + // new methods exposed + + /** + * Clean up the scene graph and the physics. + */ + void cleanup() { + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + tempModelRoot.removeFromParent(); + physicsSpace.removeTickListener(this); + tempDac.setPhysicsSpace(null); + assert physicsSpace.isEmpty(); + + if (!wasDebugEnabled) { + bulletAppState.setDebugEnabled(false); + } + } + // ************************************************************************* + // Callable methods + + /** + * Estimate the range of motion of each linked bone in the loaded C-G model. + * + * @return a new map from bone indices to ranges of motion + */ + @Override + public RangeOfMotion[] call() throws InterruptedException { + // Accumulate joint-angle statistics for 10 seconds. + Thread.sleep(10_000); + + // Convert the statistics into ranges of motion. + int numBones = model.countBones(); + RangeOfMotion[] roms = new RangeOfMotion[numBones]; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + if (model.isBoneLinked(boneIndex)) { + Vector3f max = maxima[boneIndex]; + Vector3f min = minima[boneIndex]; + roms[boneIndex] = new RangeOfMotion( + max.x, min.x, max.y, min.y, max.z, min.z); + } + } + + return roms; + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just after the simulation has been stepped. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // Read joint angles from the ragdoll and update statistics. + Vector3f angles = new Vector3f(); + int numBones = model.countBones(); + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + String boneName = model.boneName(boneIndex); + BoneLink link = tempDac.findBoneLink(boneName); + if (link != null) { + PhysicsJoint joint = link.getJoint(); + SixDofJoint sixDof = (SixDofJoint) joint; + sixDof.getAngles(angles); + assert angles.x >= -FastMath.PI : angles; + assert angles.x <= FastMath.PI : angles; + assert angles.y >= -FastMath.PI : angles; + assert angles.y <= FastMath.PI : angles; + assert angles.z >= -FastMath.PI : angles; + assert angles.z <= FastMath.PI : angles; + MyVector3f.accumulateMaxima(maxima[boneIndex], angles); + MyVector3f.accumulateMinima(minima[boneIndex], angles); + } + } + } + + /** + * Callback from Bullet, invoked just before the simulation is stepped. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Apply a pseudo-random Pose to the Armature or Skeleton of the temporary + * C-G model. + */ + @SuppressWarnings("unchecked") + private void applyRandomPose() { + // Choose an AnimComposer or AnimControl. + List controls = MySpatial.listControls( + tempModelRoot, AnimControl.class, null); + MySpatial.listControls(tempModelRoot, AnimComposer.class, controls); + AbstractControl control = (AbstractControl) generator.pick(controls); + + if (control instanceof AnimControl) { + AnimControl animControl = (AnimControl) control; + + // Choose an Animation. + Collection nameCollection = animControl.getAnimationNames(); + int numAnimations = nameCollection.size(); + String[] nameArray = new String[numAnimations]; + nameCollection.toArray(nameArray); + String animationName = generator.pick(nameArray); + if (animationName == null) { + return; + } + Animation animation = animControl.getAnim(animationName); + + // Choose an animation time. + float duration = animation.getLength(); + float animationTime = generator.nextFloat(0f, duration); + + Skeleton skeleton = tempDac.getSkeleton(); + Pose pose = new Pose(skeleton); + pose.setToAnimation(animation, animationTime, techniques); + pose.applyTo(skeleton); + + } else if (control instanceof AnimComposer) { + AnimComposer composer = (AnimComposer) control; + + // Choose an AnimClip. + Collection clips = composer.getAnimClips(); + int numClips = clips.size(); + AnimClip[] clipArray = new AnimClip[numClips]; + clips.toArray(clipArray); + AnimClip clip = generator.pick(clipArray); + if (clip == null) { + return; + } + + // Choose an animation time. + double duration = clip.getLength(); + double animationTime = duration * generator.nextDouble(); + + Armature armature = tempDac.getArmature(); + Pose pose = new Pose(armature); + pose.setToClip(clip, animationTime); + pose.applyTo(armature); + } + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/TestMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/TestMode.java index 3a249a545..b010c46b6 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/TestMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/TestMode.java @@ -1,651 +1,651 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.DacConfiguration; -import com.jme3.bullet.animation.DacLinks; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.KinematicSubmode; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.TorsoLink; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.export.JmeExporter; -import com.jme3.export.binary.BinaryExporter; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.math.Transform; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.AbstractControl; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.nifty.dialog.AllowNull; -import jme3utilities.nifty.dialog.DialogController; -import jme3utilities.nifty.dialog.FloatDialog; -import jme3utilities.ui.ActionApplication; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "test" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class TestMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(TestMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - // ************************************************************************* - // fields - - /** - * local transform of the controlled spatial when entering/exiting ragdoll - * mode - */ - final private Transform resetTransform = new Transform(); - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized input mode. - */ - TestMode() { - super("test"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - bind(Action.rebuild, KeyInput.KEY_F3); - - bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - - bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); - bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); - bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); - bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); - bindSignal("orbitLeft", KeyInput.KEY_A); - bindSignal("orbitRight", KeyInput.KEY_D); - - bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); - bind(Action.toggleSkeleton, KeyInput.KEY_V); - bind(Action.togglePhysicsDebug, KeyInput.KEY_SLASH); - - bind(Action.pickLink, "RMB"); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - handled = true; - Model model = DacWizard.getModel(); - switch (actionString) { - case Action.pickLink: - pickLink(); - break; - - case Action.previousScreen: - previousScreen(); - break; - - case Action.rebuild: - DacLinks dac = DacWizard.findDac(); - if (dac != null) { - dac.rebuild(); - } - break; - - case Action.save: - saveJava(); - break; - - case Action.saveJ3o: - saveJ3o(); - break; - - case Action.setAnimationTime: - setAnimationTime(); - break; - - case Action.setMargin: - setMargin(); - break; - - case Action.toggleAxes: - model.toggleShowingAxes(); - break; - - case Action.toggleMesh: - DacWizard.toggleMesh(); - break; - - case Action.togglePhysicsDebug: - togglePhysicsDebug(); - break; - - case Action.toggleRagdoll: - toggleRagdoll(); - break; - - case Action.toggleSkeleton: - model.toggleShowingSkeleton(); - break; - - default: - handled = false; - } - - String prefix = Action.setAnimationTime + " "; - if (!handled && actionString.startsWith(prefix)) { - String argument = MyString.remainder(actionString, prefix); - float time = Float.parseFloat(argument); - model.setAnimationTime(time); - handled = true; - } - - prefix = Action.setMargin + " "; - if (!handled && actionString.startsWith(prefix)) { - String arg = MyString.remainder(actionString, prefix); - float newMargin = Float.parseFloat(arg); - setMargin(newMargin); - handled = true; - } - } - - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Format a LinkConfig as Java source code. - * - * @param config (not null, unaffected) - * @return formatted text (not null, not empty) - */ - private static String format(LinkConfig config) { - Vector3f scale = config.shapeScale(null); - String scaleXString = MyString.describe(scale.x); - String scaleYString = MyString.describe(scale.y); - String scaleZString = MyString.describe(scale.z); - - float massP = config.massParameter(); - String massPString = MyString.describe(massP); - - RotationOrder ro = config.rotationOrder(); - String orderString = (ro == null) ? "null" : "RotationOrder." + ro; - - String code = String.format( - "new LinkConfig(%sf, MassHeuristic.%s,%n" - + " ShapeHeuristic.%s, " - + "new Vector3f(%sf, %sf, %sf),%n" - + " CenterHeuristic.%s, %s)", - massPString, config.massHeuristic(), - config.shapeHeuristic(), - scaleXString, scaleYString, scaleZString, - config.centerHeuristic(), orderString); - - return code; - } - - /** - * Cast a physics ray from the mouse pointer. If the nearest hit is a - * PhysicsLink, select that link. - */ - private void pickLink() { - Vector2f screenXY = inputManager.getCursorPosition(); - Vector3f from = cam.getWorldCoordinates(screenXY, 0f); - Vector3f to = cam.getWorldCoordinates(screenXY, 1f); - - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - List rayTest = physicsSpace.rayTest(from, to); - if (!rayTest.isEmpty()) { - PhysicsRayTestResult nearestHit = rayTest.get(0); - PhysicsCollisionObject pco = nearestHit.getCollisionObject(); - Object user = pco.getUserObject(); - if (user instanceof PhysicsLink) { - PhysicsLink link = (PhysicsLink) user; - Model model = DacWizard.getModel(); - - if (link instanceof BoneLink) { - String boneName = link.boneName(); - model.selectLink(boneName); - } else { - assert link instanceof TorsoLink; - model.selectLink(DacConfiguration.torsoName); - } - } - } - } - - /** - * Go back to the "links" screen. - */ - private void previousScreen() { - setEnabled(false); - InputMode links = InputMode.findMode("links"); - links.setEnabled(true); - } - - /** - * Write the model to a file, along with a configured control. - */ - private static void saveJ3o() { - Model model = DacWizard.getModel(); - - String originalPath = model.filePath(); - File originalFile = new File(originalPath); - String modelName = originalFile.getName(); - if (modelName.endsWith(".j3o")) { - modelName = MyString.removeSuffix(modelName, ".j3o"); - } - String hhmmss = ActionApplication.hhmmss(); - String outputFileName = String.format("%s-%s.j3o", modelName, hhmmss); - String outputFilePath = ActionApplication.filePath(outputFileName); - - Spatial modelRoot = model.getRootSpatial(); - modelRoot = Heart.deepCopy(modelRoot); - AbstractControl sControl = RagUtils.findSControl(modelRoot); - Spatial controlledSpatial = sControl.getSpatial(); - DynamicAnimControl dac = model.copyRagdoll(); - controlledSpatial.addControl(dac); - - JmeExporter exporter = BinaryExporter.getInstance(); - File outputFile = new File(outputFilePath); - TestScreen screen = DacWizard.findAppState(TestScreen.class); - try { - exporter.save(modelRoot, outputFile); - } catch (IOException exception) { - screen.showInfoDialog("Exception", exception.toString()); - return; - } - - // Display a confirmation dialog. - String message = String.format( - "The model and configured control have been written to%n%s.", - MyString.quote(outputFilePath)); - screen.showInfoDialog("Success", message); - } - - /** - * Write the control configuration to a file. - */ - private static void saveJava() { - String hhmmss = ActionApplication.hhmmss(); - String className = "Dac" + hhmmss; - String fileName = className + ".java"; - - DynamicAnimControl dac = DacWizard.findDac(); - TestScreen screen = DacWizard.findAppState(TestScreen.class); - - String path = ActionApplication.filePath(fileName); - File file = new File(path); - try (PrintStream stream = new PrintStream(file)) { - write(dac, className, stream); - } catch (FileNotFoundException exception) { - screen.showInfoDialog("Exception", exception.toString()); - return; - } - - // Display a confirmation dialog. - String message = String.format( - "The configuration has been written to%n%s.", - MyString.quote(path)); - screen.showInfoDialog("Success", message); - } - - /** - * Process a "set animationTime" action: display a dialog to enter a new - * animation time. - */ - private static void setAnimationTime() { - Model model = DacWizard.getModel(); - float duration = model.animationDuration(); - DialogController controller - = new FloatDialog("Set", 0f, duration, AllowNull.No); - - float oldTime = model.animationTime(); - String defaultText = Float.toString(oldTime); - - TestScreen screen = DacWizard.findAppState(TestScreen.class); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter the animation time (in seconds):", - defaultText, Action.setAnimationTime + " ", controller); - } - - /** - * Process a "set margin" action: display a dialog to enter a new collision - * margin. - */ - private static void setMargin() { - float oldValue = CollisionShape.getDefaultMargin(); - String defaultValue = Float.toString(oldValue); - FloatDialog controller = new FloatDialog( - "Set", Float.MIN_VALUE, Float.MAX_VALUE, AllowNull.No); - - TestScreen screen = DacWizard.findAppState(TestScreen.class); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter new margin:", defaultValue, - Action.setMargin + " ", controller); - } - - /** - * Alter the default collision margin and also the margin of every shape. - * - * @param newMargin the desired value (>0) - */ - private static void setMargin(float newMargin) { - CollisionShape.setDefaultMargin(newMargin); - - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - Collection list - = physicsSpace.getPcoList(); - for (PhysicsCollisionObject pco : list) { - pco.getCollisionShape().setMargin(newMargin); - } - } - - /** - * Toggle physics-debug visualization on/off. - */ - private static void togglePhysicsDebug() { - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - boolean enabled = bulletAppState.isDebugEnabled(); - bulletAppState.setDebugEnabled(!enabled); - } - - /** - * Toggle ragdoll mode. - */ - private void toggleRagdoll() { - DynamicAnimControl dac = DacWizard.findDac(); - TorsoLink torso = dac.getTorsoLink(); - if (torso.isKinematic()) { - // Save the local transform of the controlled spatial. - Spatial controlledSpatial = dac.getSpatial(); - Transform local = controlledSpatial.getLocalTransform(); // alias - resetTransform.set(local); - dac.saveCurrentPose(); - dac.setRagdollMode(); - - } else { // Gradually blend to the saved local transform and pose. - float blendInterval = 1f; // in seconds - dac.blendToKinematicMode( - KinematicSubmode.Reset, blendInterval, resetTransform); - } - } - - /** - * Write the control configuration to a stream, as Java source code. - * - * @param dac a configured control to reproduce (not null, unaffected) - * @param className name for the Java class (not null, not empty) - * @param stream the output stream (not null) - */ - private static void write( - DacConfiguration dac, String className, PrintStream stream) { - stream.printf("import com.jme3.bullet.RotationOrder;%n" - + "import com.jme3.bullet.animation.CenterHeuristic;%n" - + "import com.jme3.bullet.animation.DacConfiguration;%n" - + "import com.jme3.bullet.animation.DynamicAnimControl;%n" - + "import com.jme3.bullet.animation.LinkConfig;%n" - + "import com.jme3.bullet.animation.MassHeuristic;%n" - + "import com.jme3.bullet.animation.RangeOfMotion;%n" - + "import com.jme3.bullet.animation.ShapeHeuristic;%n" - + "import com.jme3.math.Vector3f;%n%n"); - stream.printf("public class %s extends DynamicAnimControl ", className); - stream.printf("{%n%n public %s() {%n", className); - stream.println(" super();"); - - // Write each unique LinkConfig. - Map configs = writeLinkConfigs(dac, stream); - - // Configure the torso of the ragdoll. - String torsoName = DacConfiguration.torsoName; - LinkConfig config = dac.config(torsoName); - int configIndex = configs.get(config); - String code = String.format(" super.setConfig(%s, config%d);%n", - MyString.quote(torsoName), configIndex); - stream.print(code); - writeConfigureMainBone(dac, "super", stream); - - writeConfigureBoneLinks(dac, "super", configs, stream); - stream.printf(" }%n%n"); - - stream.println( - " public static void configure(DacConfiguration dac) {"); - - // Write each unique LinkConfig. - configs = writeLinkConfigs(dac, stream); - - // Configure the torso of the ragdoll. - config = dac.config(torsoName); - configIndex = configs.get(config); - code = String.format(" dac.setConfig(%s, config%d);%n", - MyString.quote(torsoName), configIndex); - stream.print(code); - writeConfigureMainBone(dac, "dac", stream); - - writeConfigureBoneLinks(dac, "dac", configs, stream); - stream.printf(" }%n}%n"); - } - - /** - * Write a LinkConfig definition to a stream, as Java source code. - * - * @param config a LinkConfig to use as a model (not null) - * @param configIndex the index into unique link configurations (>0) - * @param stream the output stream (not null) - */ - private static void writeConfig( - LinkConfig config, int configIndex, PrintStream stream) { - assert config != null; - assert configIndex > 0 : configIndex; - - String newValue = format(config); - stream.printf( - " LinkConfig config%d = %s;%n", configIndex, newValue); - } - - /** - * Write code to configure each linked bone in the ragdoll. - * - * @param dac a configured control to reproduce (not null, unaffected) - * @param varName the name of the Java variable to configure (not null, not - * empty) - * @param configs map from unique link configurations to indices (not null, - * unaffected) - * @param stream the output stream (not null) - */ - private static void writeConfigureBoneLinks( - DacConfiguration dac, String varName, - Map configs, PrintStream stream) { - String[] lbNames = dac.listLinkedBoneNames(); - - for (String lbName : lbNames) { - LinkConfig config = dac.config(lbName); - int configIndex = configs.get(config); - stream.printf(" %s.link(%s, config%d,%n", - varName, MyString.quote(lbName), configIndex); - - RangeOfMotion range = dac.getJointLimits(lbName); - - float maxX = range.getMaxRotation(PhysicsSpace.AXIS_X); - String maxXString = MyString.describeAngle(maxX); - float minX = range.getMinRotation(PhysicsSpace.AXIS_X); - String minXString = MyString.describeAngle(minX); - - float maxY = range.getMaxRotation(PhysicsSpace.AXIS_Y); - String maxYString = MyString.describeAngle(maxY); - float minY = range.getMinRotation(PhysicsSpace.AXIS_Y); - String minYString = MyString.describeAngle(minY); - - float maxZ = range.getMaxRotation(PhysicsSpace.AXIS_Z); - String maxZString = MyString.describeAngle(maxZ); - float minZ = range.getMinRotation(PhysicsSpace.AXIS_Z); - String minZString = MyString.describeAngle(minZ); - - String newRange = String.format("new RangeOfMotion(" - + "%sf, %sf, %sf, %sf, %sf, %sf)", - maxXString, minXString, - maxYString, minYString, - maxZString, minZString); - stream.printf(" %s);%n", newRange); - } - } - - /** - * Write code to configure the torso's main bone. - * - * @param dac a configured control to reproduce (not null, unaffected) - * @param varName the name of the Java variable to configure (not null, not - * empty) - * @param stream the output stream (not null) - */ - private static void writeConfigureMainBone( - DacConfiguration dac, String varName, PrintStream stream) { - String mbName = dac.mainBoneName(); - stream.printf(" %s.setMainBoneName(%s);%n", varName, - MyString.quote(mbName)); - } - - /** - * Assign an index to each unique link configuration in the specified - * control and write generative Java code to the specified stream. - * - * @param dac the control to analyze - * @param stream the output stream (not null) - * @return a new map from link configurations to indices (not null) - */ - private static Map writeLinkConfigs( - DacConfiguration dac, PrintStream stream) { - int nextConfigIndex = 1; - Map result = new TreeMap<>(); - - LinkConfig config = dac.config(DacConfiguration.torsoName); - if (!result.containsKey(config)) { - result.put(config, nextConfigIndex); - writeConfig(config, nextConfigIndex, stream); - ++nextConfigIndex; - } - - String[] lbNames = dac.listLinkedBoneNames(); - for (String lbName : lbNames) { - config = dac.config(lbName); - if (!result.containsKey(config)) { - result.put(config, nextConfigIndex); - writeConfig(config, nextConfigIndex, stream); - ++nextConfigIndex; - } - } - - return result; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.DacConfiguration; +import com.jme3.bullet.animation.DacLinks; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.KinematicSubmode; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.TorsoLink; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.export.JmeExporter; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.math.Transform; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.nifty.dialog.AllowNull; +import jme3utilities.nifty.dialog.DialogController; +import jme3utilities.nifty.dialog.FloatDialog; +import jme3utilities.ui.ActionApplication; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "test" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class TestMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(TestMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + // ************************************************************************* + // fields + + /** + * local transform of the controlled spatial when entering/exiting ragdoll + * mode + */ + final private Transform resetTransform = new Transform(); + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized input mode. + */ + TestMode() { + super("test"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + bind(Action.rebuild, KeyInput.KEY_F3); + + bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + + bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); + bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); + bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); + bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); + bindSignal("orbitLeft", KeyInput.KEY_A); + bindSignal("orbitRight", KeyInput.KEY_D); + + bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); + bind(Action.toggleSkeleton, KeyInput.KEY_V); + bind(Action.togglePhysicsDebug, KeyInput.KEY_SLASH); + + bind(Action.pickLink, "RMB"); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + handled = true; + Model model = DacWizard.getModel(); + switch (actionString) { + case Action.pickLink: + pickLink(); + break; + + case Action.previousScreen: + previousScreen(); + break; + + case Action.rebuild: + DacLinks dac = DacWizard.findDac(); + if (dac != null) { + dac.rebuild(); + } + break; + + case Action.save: + saveJava(); + break; + + case Action.saveJ3o: + saveJ3o(); + break; + + case Action.setAnimationTime: + setAnimationTime(); + break; + + case Action.setMargin: + setMargin(); + break; + + case Action.toggleAxes: + model.toggleShowingAxes(); + break; + + case Action.toggleMesh: + DacWizard.toggleMesh(); + break; + + case Action.togglePhysicsDebug: + togglePhysicsDebug(); + break; + + case Action.toggleRagdoll: + toggleRagdoll(); + break; + + case Action.toggleSkeleton: + model.toggleShowingSkeleton(); + break; + + default: + handled = false; + } + + String prefix = Action.setAnimationTime + " "; + if (!handled && actionString.startsWith(prefix)) { + String argument = MyString.remainder(actionString, prefix); + float time = Float.parseFloat(argument); + model.setAnimationTime(time); + handled = true; + } + + prefix = Action.setMargin + " "; + if (!handled && actionString.startsWith(prefix)) { + String arg = MyString.remainder(actionString, prefix); + float newMargin = Float.parseFloat(arg); + setMargin(newMargin); + handled = true; + } + } + + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Format a LinkConfig as Java source code. + * + * @param config (not null, unaffected) + * @return formatted text (not null, not empty) + */ + private static String format(LinkConfig config) { + Vector3f scale = config.shapeScale(null); + String scaleXString = MyString.describe(scale.x); + String scaleYString = MyString.describe(scale.y); + String scaleZString = MyString.describe(scale.z); + + float massP = config.massParameter(); + String massPString = MyString.describe(massP); + + RotationOrder ro = config.rotationOrder(); + String orderString = (ro == null) ? "null" : "RotationOrder." + ro; + + String code = String.format( + "new LinkConfig(%sf, MassHeuristic.%s,%n" + + " ShapeHeuristic.%s, " + + "new Vector3f(%sf, %sf, %sf),%n" + + " CenterHeuristic.%s, %s)", + massPString, config.massHeuristic(), + config.shapeHeuristic(), + scaleXString, scaleYString, scaleZString, + config.centerHeuristic(), orderString); + + return code; + } + + /** + * Cast a physics ray from the mouse pointer. If the nearest hit is a + * PhysicsLink, select that link. + */ + private void pickLink() { + Vector2f screenXY = inputManager.getCursorPosition(); + Vector3f from = cam.getWorldCoordinates(screenXY, 0f); + Vector3f to = cam.getWorldCoordinates(screenXY, 1f); + + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + List rayTest = physicsSpace.rayTest(from, to); + if (!rayTest.isEmpty()) { + PhysicsRayTestResult nearestHit = rayTest.get(0); + PhysicsCollisionObject pco = nearestHit.getCollisionObject(); + Object user = pco.getUserObject(); + if (user instanceof PhysicsLink) { + PhysicsLink link = (PhysicsLink) user; + Model model = DacWizard.getModel(); + + if (link instanceof BoneLink) { + String boneName = link.boneName(); + model.selectLink(boneName); + } else { + assert link instanceof TorsoLink; + model.selectLink(DacConfiguration.torsoName); + } + } + } + } + + /** + * Go back to the "links" screen. + */ + private void previousScreen() { + setEnabled(false); + InputMode links = InputMode.findMode("links"); + links.setEnabled(true); + } + + /** + * Write the model to a file, along with a configured control. + */ + private static void saveJ3o() { + Model model = DacWizard.getModel(); + + String originalPath = model.filePath(); + File originalFile = new File(originalPath); + String modelName = originalFile.getName(); + if (modelName.endsWith(".j3o")) { + modelName = MyString.removeSuffix(modelName, ".j3o"); + } + String hhmmss = ActionApplication.hhmmss(); + String outputFileName = String.format("%s-%s.j3o", modelName, hhmmss); + String outputFilePath = ActionApplication.filePath(outputFileName); + + Spatial modelRoot = model.getRootSpatial(); + modelRoot = Heart.deepCopy(modelRoot); + AbstractControl sControl = RagUtils.findSControl(modelRoot); + Spatial controlledSpatial = sControl.getSpatial(); + DynamicAnimControl dac = model.copyRagdoll(); + controlledSpatial.addControl(dac); + + JmeExporter exporter = BinaryExporter.getInstance(); + File outputFile = new File(outputFilePath); + TestScreen screen = DacWizard.findAppState(TestScreen.class); + try { + exporter.save(modelRoot, outputFile); + } catch (IOException exception) { + screen.showInfoDialog("Exception", exception.toString()); + return; + } + + // Display a confirmation dialog. + String message = String.format( + "The model and configured control have been written to%n%s.", + MyString.quote(outputFilePath)); + screen.showInfoDialog("Success", message); + } + + /** + * Write the control configuration to a file. + */ + private static void saveJava() { + String hhmmss = ActionApplication.hhmmss(); + String className = "Dac" + hhmmss; + String fileName = className + ".java"; + + DynamicAnimControl dac = DacWizard.findDac(); + TestScreen screen = DacWizard.findAppState(TestScreen.class); + + String path = ActionApplication.filePath(fileName); + File file = new File(path); + try (PrintStream stream = new PrintStream(file)) { + write(dac, className, stream); + } catch (FileNotFoundException exception) { + screen.showInfoDialog("Exception", exception.toString()); + return; + } + + // Display a confirmation dialog. + String message = String.format( + "The configuration has been written to%n%s.", + MyString.quote(path)); + screen.showInfoDialog("Success", message); + } + + /** + * Process a "set animationTime" action: display a dialog to enter a new + * animation time. + */ + private static void setAnimationTime() { + Model model = DacWizard.getModel(); + float duration = model.animationDuration(); + DialogController controller + = new FloatDialog("Set", 0f, duration, AllowNull.No); + + float oldTime = model.animationTime(); + String defaultText = Float.toString(oldTime); + + TestScreen screen = DacWizard.findAppState(TestScreen.class); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter the animation time (in seconds):", + defaultText, Action.setAnimationTime + " ", controller); + } + + /** + * Process a "set margin" action: display a dialog to enter a new collision + * margin. + */ + private static void setMargin() { + float oldValue = CollisionShape.getDefaultMargin(); + String defaultValue = Float.toString(oldValue); + FloatDialog controller = new FloatDialog( + "Set", Float.MIN_VALUE, Float.MAX_VALUE, AllowNull.No); + + TestScreen screen = DacWizard.findAppState(TestScreen.class); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter new margin:", defaultValue, + Action.setMargin + " ", controller); + } + + /** + * Alter the default collision margin and also the margin of every shape. + * + * @param newMargin the desired value (>0) + */ + private static void setMargin(float newMargin) { + CollisionShape.setDefaultMargin(newMargin); + + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + Collection list + = physicsSpace.getPcoList(); + for (PhysicsCollisionObject pco : list) { + pco.getCollisionShape().setMargin(newMargin); + } + } + + /** + * Toggle physics-debug visualization on/off. + */ + private static void togglePhysicsDebug() { + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + boolean enabled = bulletAppState.isDebugEnabled(); + bulletAppState.setDebugEnabled(!enabled); + } + + /** + * Toggle ragdoll mode. + */ + private void toggleRagdoll() { + DynamicAnimControl dac = DacWizard.findDac(); + TorsoLink torso = dac.getTorsoLink(); + if (torso.isKinematic()) { + // Save the local transform of the controlled spatial. + Spatial controlledSpatial = dac.getSpatial(); + Transform local = controlledSpatial.getLocalTransform(); // alias + resetTransform.set(local); + dac.saveCurrentPose(); + dac.setRagdollMode(); + + } else { // Gradually blend to the saved local transform and pose. + float blendInterval = 1f; // in seconds + dac.blendToKinematicMode( + KinematicSubmode.Reset, blendInterval, resetTransform); + } + } + + /** + * Write the control configuration to a stream, as Java source code. + * + * @param dac a configured control to reproduce (not null, unaffected) + * @param className name for the Java class (not null, not empty) + * @param stream the output stream (not null) + */ + private static void write( + DacConfiguration dac, String className, PrintStream stream) { + stream.printf("import com.jme3.bullet.RotationOrder;%n" + + "import com.jme3.bullet.animation.CenterHeuristic;%n" + + "import com.jme3.bullet.animation.DacConfiguration;%n" + + "import com.jme3.bullet.animation.DynamicAnimControl;%n" + + "import com.jme3.bullet.animation.LinkConfig;%n" + + "import com.jme3.bullet.animation.MassHeuristic;%n" + + "import com.jme3.bullet.animation.RangeOfMotion;%n" + + "import com.jme3.bullet.animation.ShapeHeuristic;%n" + + "import com.jme3.math.Vector3f;%n%n"); + stream.printf("public class %s extends DynamicAnimControl ", className); + stream.printf("{%n%n public %s() {%n", className); + stream.println(" super();"); + + // Write each unique LinkConfig. + Map configs = writeLinkConfigs(dac, stream); + + // Configure the torso of the ragdoll. + String torsoName = DacConfiguration.torsoName; + LinkConfig config = dac.config(torsoName); + int configIndex = configs.get(config); + String code = String.format(" super.setConfig(%s, config%d);%n", + MyString.quote(torsoName), configIndex); + stream.print(code); + writeConfigureMainBone(dac, "super", stream); + + writeConfigureBoneLinks(dac, "super", configs, stream); + stream.printf(" }%n%n"); + + stream.println( + " public static void configure(DacConfiguration dac) {"); + + // Write each unique LinkConfig. + configs = writeLinkConfigs(dac, stream); + + // Configure the torso of the ragdoll. + config = dac.config(torsoName); + configIndex = configs.get(config); + code = String.format(" dac.setConfig(%s, config%d);%n", + MyString.quote(torsoName), configIndex); + stream.print(code); + writeConfigureMainBone(dac, "dac", stream); + + writeConfigureBoneLinks(dac, "dac", configs, stream); + stream.printf(" }%n}%n"); + } + + /** + * Write a LinkConfig definition to a stream, as Java source code. + * + * @param config a LinkConfig to use as a model (not null) + * @param configIndex the index into unique link configurations (>0) + * @param stream the output stream (not null) + */ + private static void writeConfig( + LinkConfig config, int configIndex, PrintStream stream) { + assert config != null; + assert configIndex > 0 : configIndex; + + String newValue = format(config); + stream.printf( + " LinkConfig config%d = %s;%n", configIndex, newValue); + } + + /** + * Write code to configure each linked bone in the ragdoll. + * + * @param dac a configured control to reproduce (not null, unaffected) + * @param varName the name of the Java variable to configure (not null, not + * empty) + * @param configs map from unique link configurations to indices (not null, + * unaffected) + * @param stream the output stream (not null) + */ + private static void writeConfigureBoneLinks( + DacConfiguration dac, String varName, + Map configs, PrintStream stream) { + String[] lbNames = dac.listLinkedBoneNames(); + + for (String lbName : lbNames) { + LinkConfig config = dac.config(lbName); + int configIndex = configs.get(config); + stream.printf(" %s.link(%s, config%d,%n", + varName, MyString.quote(lbName), configIndex); + + RangeOfMotion range = dac.getJointLimits(lbName); + + float maxX = range.getMaxRotation(PhysicsSpace.AXIS_X); + String maxXString = MyString.describeAngle(maxX); + float minX = range.getMinRotation(PhysicsSpace.AXIS_X); + String minXString = MyString.describeAngle(minX); + + float maxY = range.getMaxRotation(PhysicsSpace.AXIS_Y); + String maxYString = MyString.describeAngle(maxY); + float minY = range.getMinRotation(PhysicsSpace.AXIS_Y); + String minYString = MyString.describeAngle(minY); + + float maxZ = range.getMaxRotation(PhysicsSpace.AXIS_Z); + String maxZString = MyString.describeAngle(maxZ); + float minZ = range.getMinRotation(PhysicsSpace.AXIS_Z); + String minZString = MyString.describeAngle(minZ); + + String newRange = String.format("new RangeOfMotion(" + + "%sf, %sf, %sf, %sf, %sf, %sf)", + maxXString, minXString, + maxYString, minYString, + maxZString, minZString); + stream.printf(" %s);%n", newRange); + } + } + + /** + * Write code to configure the torso's main bone. + * + * @param dac a configured control to reproduce (not null, unaffected) + * @param varName the name of the Java variable to configure (not null, not + * empty) + * @param stream the output stream (not null) + */ + private static void writeConfigureMainBone( + DacConfiguration dac, String varName, PrintStream stream) { + String mbName = dac.mainBoneName(); + stream.printf(" %s.setMainBoneName(%s);%n", varName, + MyString.quote(mbName)); + } + + /** + * Assign an index to each unique link configuration in the specified + * control and write generative Java code to the specified stream. + * + * @param dac the control to analyze + * @param stream the output stream (not null) + * @return a new map from link configurations to indices (not null) + */ + private static Map writeLinkConfigs( + DacConfiguration dac, PrintStream stream) { + int nextConfigIndex = 1; + Map result = new TreeMap<>(); + + LinkConfig config = dac.config(DacConfiguration.torsoName); + if (!result.containsKey(config)) { + result.put(config, nextConfigIndex); + writeConfig(config, nextConfigIndex, stream); + ++nextConfigIndex; + } + + String[] lbNames = dac.listLinkedBoneNames(); + for (String lbName : lbNames) { + config = dac.config(lbName); + if (!result.containsKey(config)) { + result.put(config, nextConfigIndex); + writeConfig(config, nextConfigIndex, stream); + ++nextConfigIndex; + } + } + + return result; + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/TestScreen.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/TestScreen.java index 5222ffece..48fa008bb 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/TestScreen.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/TestScreen.java @@ -1,452 +1,452 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.animation.Skeleton; -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.DacConfiguration; -import com.jme3.bullet.animation.DacLinks; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.TorsoLink; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.joints.Constraint; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Plane; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.AbstractControl; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InitialState; -import jme3utilities.MyAsset; -import jme3utilities.MyString; -import jme3utilities.debug.AxesVisualizer; -import jme3utilities.debug.SkeletonVisualizer; -import jme3utilities.math.MyMath; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "test" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class TestScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(TestScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * animation time of the pose being viewed (in seconds) - */ - private float viewedAnimationTime; - /** - * debug material for the selected PhysicsLink - */ - private Material selectMaterial; - /** - * horizontal plane added to physics space, or null if not added - */ - private PhysicsRigidBody groundPlane = null; - /** - * root spatial of the C-G model being viewed, or null for none - */ - private Spatial viewedSpatial = null; - /** - * clip/animation name of the pose being viewed - */ - private String viewedAnimationName; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - TestScreen() { - super("test", "Interface/Nifty/screens/wizard/test.xml", - InitialState.Disabled); - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("test"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - - if (selectMaterial == null) { - this.selectMaterial = MyAsset.createWireframeMaterial( - assetManager, ColorRGBA.White); - } - } - - /** - * A callback from Nifty, invoked each time this screen shuts down. - */ - @Override - public void onEndScreen() { - super.onEndScreen(); - removeGroundPlane(); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - removeGroundPlane(); - DacWizard wizard = DacWizard.getApplication(); - wizard.clearScene(); - this.viewedSpatial = null; - this.viewedAnimationName = null; - this.viewedAnimationTime = Float.NaN; - - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - bulletAppState.setDebugEnabled(true); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - updateMarginButton(); - updateRagdollButton(); - updateViewButtons(); - - // Update the 3-D scene. - Model model = DacWizard.getModel(); - Spatial nextSpatial = model.getRootSpatial(); - String nextAnimationName = model.animationName(); - float nextAnimationTime = model.animationTime(); - if (nextSpatial != viewedSpatial - || !nextAnimationName.equals(viewedAnimationName) - || nextAnimationTime != viewedAnimationTime) { - DacWizard wizard = DacWizard.getApplication(); - - removeGroundPlane(); - wizard.clearScene(); - - this.viewedSpatial = nextSpatial; - this.viewedAnimationName = nextAnimationName; - this.viewedAnimationTime = nextAnimationTime; - - if (nextSpatial != null) { - Spatial cgModel = Heart.deepCopy(nextSpatial); - wizard.makeScene(cgModel, nextAnimationName, nextAnimationTime); - addGroundPlane(); - - AbstractControl sControl = RagUtils.findSControl(cgModel); - Spatial controlledSpatial = sControl.getSpatial(); - DynamicAnimControl dac = model.copyRagdoll(); - controlledSpatial.addControl(dac); - - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - dac.setPhysicsSpace(physicsSpace); - } - } - - updateAxes(); - updatePosingControls(); - updateSelectedLink(); - } - // ************************************************************************* - // private methods - - /** - * If there isn't a ground plane, create one and add it to the PhysicsSpace. - */ - private void addGroundPlane() { - if (groundPlane == null) { - Plane xzPlane = new Plane(Vector3f.UNIT_Y, 0f); - PlaneCollisionShape shape = new PlaneCollisionShape(xzPlane); - this.groundPlane - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - physicsSpace.addCollisionObject(groundPlane); - } - } - - /** - * Apply the pivot-to-PhysicsSpace transform of the specified Constraint to - * the specified Spatial. - * - * @param constraint the constraint to analyze (not null) - * @param spatial where to apply the transform (not null) - */ - private static void applyTransform(Constraint constraint, Spatial spatial) { - Transform frame = new Transform(); // TODO garbage - if (constraint instanceof New6Dof) { - New6Dof new6dof = (New6Dof) constraint; - new6dof.getFrameTransform(JointEnd.A, frame); - } else { - SixDofJoint sixDof = (SixDofJoint) constraint; - sixDof.getFrameTransform(JointEnd.A, frame); - } - - PhysicsRigidBody bodyA = constraint.getBodyA(); - Transform bodyTransform = bodyA.getTransform(null); - bodyTransform.setScale(1f); - MyMath.combine(frame, bodyTransform, frame); - - spatial.setLocalTransform(frame); - } - - /** - * Remove the ground plane (if any) from the PhysicsSpace. - */ - private void removeGroundPlane() { - if (groundPlane != null) { - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - physicsSpace.removeCollisionObject(groundPlane); - this.groundPlane = null; - } - } - - /** - * Update the AxesVisualizer. - */ - private void updateAxes() { - AxesVisualizer axesVisualizer = DacWizard.findAxesVisualizer(); - - Model model = DacWizard.getModel(); - boolean showingAxes = model.isShowingAxes(); - String btName = model.selectedLink(); - - if (!showingAxes - || viewedSpatial == null - || btName.equals(DacConfiguration.torsoName)) { - axesVisualizer.setEnabled(false); - - } else { - // Align the visualizer axes with the PhysicsJoint. - DacLinks dac = DacWizard.findDac(); - PhysicsLink selectedLink = dac.findBoneLink(btName); - Constraint constraint = (Constraint) selectedLink.getJoint(); - Spatial axesNode = axesVisualizer.getSpatial(); - applyTransform(constraint, axesNode); - - axesVisualizer.setEnabled(true); - } - } - - /** - * Update the collision-margin button. - */ - private void updateMarginButton() { - float margin = CollisionShape.getDefaultMargin(); - String marginButton = Float.toString(margin); - setButtonText("margin", marginButton); - } - - /** - * Update the posing controls. - */ - private void updatePosingControls() { - String anText = ""; - String atText = ""; - String naText = ""; - String paText = ""; - - if (viewedSpatial != null) { - Model model = DacWizard.getModel(); - int numAnimations = model.countAnimations(); - if (numAnimations > 0) { - paText = "-"; - naText = "+"; - } - - float duration = model.animationDuration(); - if (duration > 0f) { - atText = Float.toString(viewedAnimationTime) + " seconds"; - } - - anText = viewedAnimationName; - if (!anText.equals(DacWizard.bindPoseName)) { - anText = MyString.quote(anText); - } - } - - setStatusText("animationName", anText); - setButtonText("animationTime", atText); - setButtonText("nextAnimation", naText); - setButtonText("previousAnimation", paText); - } - - /** - * Update the "Go limp" button. - */ - private void updateRagdollButton() { - DacLinks dac = DacWizard.findDac(); - - String ragdollButton = ""; - if (dac != null && dac.isReady()) { - TorsoLink torso = dac.getTorsoLink(); - if (torso.isKinematic()) { - ragdollButton = "Go limp"; - } else { - ragdollButton = "Reset model"; - } - } - setButtonText("ragdoll", ragdollButton); - } - - /** - * Update the linkNameStatus and the custom materials of the DAC's bodies. - */ - private void updateSelectedLink() { - DacLinks dac = DacWizard.findDac(); - String linkName = ""; - if (dac != null) { - Model model = DacWizard.getModel(); - String selectedBtName = model.selectedLink(); - - if (selectedBtName.equals(DacConfiguration.torsoName)) { - linkName = "Torso:"; - } else { - linkName = "Bone:" + selectedBtName; - } - - List boneLinks = dac.listLinks(BoneLink.class); - for (BoneLink link : boneLinks) { - String boneName = link.boneName(); - PhysicsRigidBody body = link.getRigidBody(); - if (boneName.equals(selectedBtName)) { - body.setDebugMaterial(selectMaterial); - } else { - body.setDebugMaterial(null); - } - } - TorsoLink link = dac.getTorsoLink(); - PhysicsRigidBody body = link.getRigidBody(); - if (selectedBtName.equals(DacConfiguration.torsoName)) { - body.setDebugMaterial(selectMaterial); - } else { - body.setDebugMaterial(null); - } - } - setStatusText("linkNameStatus", linkName); - } - - /** - * Update the buttons that toggle view elements. - */ - private void updateViewButtons() { - BulletAppState bulletAppState - = DacWizard.findAppState(BulletAppState.class); - - String debugButton; - if (bulletAppState.isDebugEnabled()) { - debugButton = "Hide physics"; - } else { - debugButton = "Show physics"; - } - setButtonText("debug", debugButton); - - Model model = DacWizard.getModel(); - String meshButton; - if (model.isShowingMeshes()) { - meshButton = "Hide meshes"; - } else { - meshButton = "Show meshes"; - } - setButtonText("mesh", meshButton); - - String skeletonText = ""; - DacWizard app = DacWizard.getApplication(); - SkeletonVisualizer sv = app.findSkeletonVisualizer(); - Spatial root = model.getRootSpatial(); - if (sv != null && root != null) { - boolean isShown = model.isShowingSkeleton(); - sv.setEnabled(isShown); - - Skeleton skeleton = model.findSkeleton(); - String armature = (skeleton == null) ? "armature" : "skeleton"; - if (isShown) { - skeletonText = "Hide " + armature; - } else { - skeletonText = "Show " + armature; - } - } - setButtonText("skeleton", skeletonText); - - String axesText = model.isShowingAxes() ? "Hide axes" : "Show axes"; - setButtonText("axes", axesText); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.animation.Skeleton; +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.DacConfiguration; +import com.jme3.bullet.animation.DacLinks; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.TorsoLink; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.joints.Constraint; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Plane; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InitialState; +import jme3utilities.MyAsset; +import jme3utilities.MyString; +import jme3utilities.debug.AxesVisualizer; +import jme3utilities.debug.SkeletonVisualizer; +import jme3utilities.math.MyMath; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "test" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class TestScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(TestScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * animation time of the pose being viewed (in seconds) + */ + private float viewedAnimationTime; + /** + * debug material for the selected PhysicsLink + */ + private Material selectMaterial; + /** + * horizontal plane added to physics space, or null if not added + */ + private PhysicsRigidBody groundPlane = null; + /** + * root spatial of the C-G model being viewed, or null for none + */ + private Spatial viewedSpatial = null; + /** + * clip/animation name of the pose being viewed + */ + private String viewedAnimationName; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + TestScreen() { + super("test", "Interface/Nifty/screens/wizard/test.xml", + InitialState.Disabled); + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("test"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + + if (selectMaterial == null) { + this.selectMaterial = MyAsset.createWireframeMaterial( + assetManager, ColorRGBA.White); + } + } + + /** + * A callback from Nifty, invoked each time this screen shuts down. + */ + @Override + public void onEndScreen() { + super.onEndScreen(); + removeGroundPlane(); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + removeGroundPlane(); + DacWizard wizard = DacWizard.getApplication(); + wizard.clearScene(); + this.viewedSpatial = null; + this.viewedAnimationName = null; + this.viewedAnimationTime = Float.NaN; + + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + bulletAppState.setDebugEnabled(true); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + updateMarginButton(); + updateRagdollButton(); + updateViewButtons(); + + // Update the 3-D scene. + Model model = DacWizard.getModel(); + Spatial nextSpatial = model.getRootSpatial(); + String nextAnimationName = model.animationName(); + float nextAnimationTime = model.animationTime(); + if (nextSpatial != viewedSpatial + || !nextAnimationName.equals(viewedAnimationName) + || nextAnimationTime != viewedAnimationTime) { + DacWizard wizard = DacWizard.getApplication(); + + removeGroundPlane(); + wizard.clearScene(); + + this.viewedSpatial = nextSpatial; + this.viewedAnimationName = nextAnimationName; + this.viewedAnimationTime = nextAnimationTime; + + if (nextSpatial != null) { + Spatial cgModel = Heart.deepCopy(nextSpatial); + wizard.makeScene(cgModel, nextAnimationName, nextAnimationTime); + addGroundPlane(); + + AbstractControl sControl = RagUtils.findSControl(cgModel); + Spatial controlledSpatial = sControl.getSpatial(); + DynamicAnimControl dac = model.copyRagdoll(); + controlledSpatial.addControl(dac); + + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + dac.setPhysicsSpace(physicsSpace); + } + } + + updateAxes(); + updatePosingControls(); + updateSelectedLink(); + } + // ************************************************************************* + // private methods + + /** + * If there isn't a ground plane, create one and add it to the PhysicsSpace. + */ + private void addGroundPlane() { + if (groundPlane == null) { + Plane xzPlane = new Plane(Vector3f.UNIT_Y, 0f); + PlaneCollisionShape shape = new PlaneCollisionShape(xzPlane); + this.groundPlane + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + physicsSpace.addCollisionObject(groundPlane); + } + } + + /** + * Apply the pivot-to-PhysicsSpace transform of the specified Constraint to + * the specified Spatial. + * + * @param constraint the constraint to analyze (not null) + * @param spatial where to apply the transform (not null) + */ + private static void applyTransform(Constraint constraint, Spatial spatial) { + Transform frame = new Transform(); // TODO garbage + if (constraint instanceof New6Dof) { + New6Dof new6dof = (New6Dof) constraint; + new6dof.getFrameTransform(JointEnd.A, frame); + } else { + SixDofJoint sixDof = (SixDofJoint) constraint; + sixDof.getFrameTransform(JointEnd.A, frame); + } + + PhysicsRigidBody bodyA = constraint.getBodyA(); + Transform bodyTransform = bodyA.getTransform(null); + bodyTransform.setScale(1f); + MyMath.combine(frame, bodyTransform, frame); + + spatial.setLocalTransform(frame); + } + + /** + * Remove the ground plane (if any) from the PhysicsSpace. + */ + private void removeGroundPlane() { + if (groundPlane != null) { + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + physicsSpace.removeCollisionObject(groundPlane); + this.groundPlane = null; + } + } + + /** + * Update the AxesVisualizer. + */ + private void updateAxes() { + AxesVisualizer axesVisualizer = DacWizard.findAxesVisualizer(); + + Model model = DacWizard.getModel(); + boolean showingAxes = model.isShowingAxes(); + String btName = model.selectedLink(); + + if (!showingAxes + || viewedSpatial == null + || btName.equals(DacConfiguration.torsoName)) { + axesVisualizer.setEnabled(false); + + } else { + // Align the visualizer axes with the PhysicsJoint. + DacLinks dac = DacWizard.findDac(); + PhysicsLink selectedLink = dac.findBoneLink(btName); + Constraint constraint = (Constraint) selectedLink.getJoint(); + Spatial axesNode = axesVisualizer.getSpatial(); + applyTransform(constraint, axesNode); + + axesVisualizer.setEnabled(true); + } + } + + /** + * Update the collision-margin button. + */ + private void updateMarginButton() { + float margin = CollisionShape.getDefaultMargin(); + String marginButton = Float.toString(margin); + setButtonText("margin", marginButton); + } + + /** + * Update the posing controls. + */ + private void updatePosingControls() { + String anText = ""; + String atText = ""; + String naText = ""; + String paText = ""; + + if (viewedSpatial != null) { + Model model = DacWizard.getModel(); + int numAnimations = model.countAnimations(); + if (numAnimations > 0) { + paText = "-"; + naText = "+"; + } + + float duration = model.animationDuration(); + if (duration > 0f) { + atText = Float.toString(viewedAnimationTime) + " seconds"; + } + + anText = viewedAnimationName; + if (!anText.equals(DacWizard.bindPoseName)) { + anText = MyString.quote(anText); + } + } + + setStatusText("animationName", anText); + setButtonText("animationTime", atText); + setButtonText("nextAnimation", naText); + setButtonText("previousAnimation", paText); + } + + /** + * Update the "Go limp" button. + */ + private void updateRagdollButton() { + DacLinks dac = DacWizard.findDac(); + + String ragdollButton = ""; + if (dac != null && dac.isReady()) { + TorsoLink torso = dac.getTorsoLink(); + if (torso.isKinematic()) { + ragdollButton = "Go limp"; + } else { + ragdollButton = "Reset model"; + } + } + setButtonText("ragdoll", ragdollButton); + } + + /** + * Update the linkNameStatus and the custom materials of the DAC's bodies. + */ + private void updateSelectedLink() { + DacLinks dac = DacWizard.findDac(); + String linkName = ""; + if (dac != null) { + Model model = DacWizard.getModel(); + String selectedBtName = model.selectedLink(); + + if (selectedBtName.equals(DacConfiguration.torsoName)) { + linkName = "Torso:"; + } else { + linkName = "Bone:" + selectedBtName; + } + + List boneLinks = dac.listLinks(BoneLink.class); + for (BoneLink link : boneLinks) { + String boneName = link.boneName(); + PhysicsRigidBody body = link.getRigidBody(); + if (boneName.equals(selectedBtName)) { + body.setDebugMaterial(selectMaterial); + } else { + body.setDebugMaterial(null); + } + } + TorsoLink link = dac.getTorsoLink(); + PhysicsRigidBody body = link.getRigidBody(); + if (selectedBtName.equals(DacConfiguration.torsoName)) { + body.setDebugMaterial(selectMaterial); + } else { + body.setDebugMaterial(null); + } + } + setStatusText("linkNameStatus", linkName); + } + + /** + * Update the buttons that toggle view elements. + */ + private void updateViewButtons() { + BulletAppState bulletAppState + = DacWizard.findAppState(BulletAppState.class); + + String debugButton; + if (bulletAppState.isDebugEnabled()) { + debugButton = "Hide physics"; + } else { + debugButton = "Show physics"; + } + setButtonText("debug", debugButton); + + Model model = DacWizard.getModel(); + String meshButton; + if (model.isShowingMeshes()) { + meshButton = "Hide meshes"; + } else { + meshButton = "Show meshes"; + } + setButtonText("mesh", meshButton); + + String skeletonText = ""; + DacWizard app = DacWizard.getApplication(); + SkeletonVisualizer sv = app.findSkeletonVisualizer(); + Spatial root = model.getRootSpatial(); + if (sv != null && root != null) { + boolean isShown = model.isShowingSkeleton(); + sv.setEnabled(isShown); + + Skeleton skeleton = model.findSkeleton(); + String armature = (skeleton == null) ? "armature" : "skeleton"; + if (isShown) { + skeletonText = "Hide " + armature; + } else { + skeletonText = "Show " + armature; + } + } + setButtonText("skeleton", skeletonText); + + String axesText = model.isShowingAxes() ? "Hide axes" : "Show axes"; + setButtonText("axes", axesText); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoMode.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoMode.java index c21a658e9..d2e92e2ed 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoMode.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoMode.java @@ -1,180 +1,180 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.bullet.animation.DacConfiguration; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "torso" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class TorsoMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(TorsoMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized input mode. - */ - TorsoMode() { - super("torso"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.previousScreen, KeyInput.KEY_PGUP); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - bind(Action.nextScreen, KeyInput.KEY_PGDN); - - bind(Action.previousScreen, KeyInput.KEY_B); - bind(Action.nextScreen, KeyInput.KEY_N); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Set the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - switch (actionString) { - case Action.editLinks: - goLinks(); - handled = true; - break; - - case Action.nextScreen: - goRomEstimation(); - handled = true; - break; - - case Action.previousScreen: - previousScreen(); - handled = true; - break; - - default: - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Bypass RoM estimation and go directly to the "links" screen. - */ - private void goLinks() { - setEnabled(false); - Model model = DacWizard.getModel(); - model.selectLink(DacConfiguration.torsoName); - InputMode links = InputMode.findMode("links"); - links.setEnabled(true); - } - - /** - * Initiate range-of-motion estimation before advancing to the "links" - * screen. - */ - private void goRomEstimation() { - setEnabled(false); - Model model = DacWizard.getModel(); - model.startRomTask(); - } - - /** - * Go back to the "bones" screen. - */ - private void previousScreen() { - setEnabled(false); - InputMode load = InputMode.findMode("bones"); - load.setEnabled(true); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.animation.DacConfiguration; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "torso" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class TorsoMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(TorsoMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized input mode. + */ + TorsoMode() { + super("torso"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.previousScreen, KeyInput.KEY_PGUP); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + bind(Action.nextScreen, KeyInput.KEY_PGDN); + + bind(Action.previousScreen, KeyInput.KEY_B); + bind(Action.nextScreen, KeyInput.KEY_N); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Set the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + switch (actionString) { + case Action.editLinks: + goLinks(); + handled = true; + break; + + case Action.nextScreen: + goRomEstimation(); + handled = true; + break; + + case Action.previousScreen: + previousScreen(); + handled = true; + break; + + default: + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Bypass RoM estimation and go directly to the "links" screen. + */ + private void goLinks() { + setEnabled(false); + Model model = DacWizard.getModel(); + model.selectLink(DacConfiguration.torsoName); + InputMode links = InputMode.findMode("links"); + links.setEnabled(true); + } + + /** + * Initiate range-of-motion estimation before advancing to the "links" + * screen. + */ + private void goRomEstimation() { + setEnabled(false); + Model model = DacWizard.getModel(); + model.startRomTask(); + } + + /** + * Go back to the "bones" screen. + */ + private void previousScreen() { + setEnabled(false); + InputMode load = InputMode.findMode("bones"); + load.setEnabled(true); + } +} diff --git a/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoScreen.java b/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoScreen.java index c7fc04308..b33ef500f 100644 --- a/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoScreen.java +++ b/DacWizard/src/main/java/jme3utilities/minie/wizard/TorsoScreen.java @@ -1,227 +1,227 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.wizard; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.controls.TreeBox; -import de.lessvoid.nifty.controls.TreeItem; -import de.lessvoid.nifty.elements.Element; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.InitialState; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "torso" screen of DacWizard. - * - * @author Stephen Gold sgold@sonic.net - */ -class TorsoScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(TorsoScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of the GUI button to proceed to the "links" screen - */ - private Element linksElement; - /** - * TreeBox to display bones managed by the torso - */ - private TreeBox treeBox; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - TorsoScreen() { - super("torso", "Interface/Nifty/screens/wizard/torso.xml", - InitialState.Disabled); - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("torso"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time the screen shuts down. - */ - @Override - public void onEndScreen() { - treeBox.clear(); - super.onEndScreen(); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - @SuppressWarnings("unchecked") - public void onStartScreen() { - super.onStartScreen(); - - Button linksButton = getButton("links"); - if (linksButton == null) { - throw new RuntimeException("missing GUI control: linksButton"); - } - this.linksElement = linksButton.getElement(); - - this.treeBox = getScreen().findNiftyControl("skeleton", TreeBox.class); - if (treeBox == null) { - throw new RuntimeException("missing GUI control: skeleton"); - } - - TreeItem rootItem = new TreeItem<>(); - rootItem.setExpanded(true); - Model model = DacWizard.getModel(); - int[] managedBones = model.listTorsoManagedBones(); - int numManaged = managedBones.length; - - // Create an item for each bone managed by the torso. - TreeItem[] boneItems = new TreeItem[numManaged]; - for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) { - int boneIndex = managedBones[managedIndex]; - BoneValue value = new BoneValue(boneIndex); - boneItems[managedIndex] = new TreeItem<>(value); - boneItems[managedIndex].setExpanded(true); - } - - // Generate a map from bone indices to managed-bone indices. - int numBones = model.countBones(); - int[] mbiArray = new int[numBones]; - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - mbiArray[boneIndex] = -1; - } - for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) { - int boneIndex = managedBones[managedIndex]; - mbiArray[boneIndex] = managedIndex; - } - - // Parent each item. - for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) { - TreeItem childItem = boneItems[managedIndex]; - int boneIndex = managedBones[managedIndex]; - int parentIndex = model.parentIndex(boneIndex); - if (parentIndex == -1) { - rootItem.addTreeItem(childItem); - } else { - int parentMbi = mbiArray[parentIndex]; - boneItems[parentMbi].addTreeItem(childItem); - } - } - treeBox.setTree(rootItem); - - // Pre-select the item for the main bone. - int mainBoneIndex = model.mainBoneIndex(); - selectItem(mainBoneIndex); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - Model model = DacWizard.getModel(); - List> selectedItems = treeBox.getSelection(); - int numSelected = selectedItems.size(); - if (numSelected == 1) { // Update the main-bone index in the model. - TreeItem mbItem = selectedItems.get(0); - BoneValue mbValue = mbItem.getValue(); - int mbIndex = mbValue.boneIndex(); - model.setMainBoneIndex(mbIndex); - - } else { // empty selection: re-select the main bone - int mainBoneIndex = model.mainBoneIndex(); - selectItem(mainBoneIndex); - } - /* - * If there's a ragdoll already configured, allow the user to - * bypass range-of-motion estimation. - */ - boolean hasRagdoll = model.hasConfiguredRagdoll(); - if (hasRagdoll) { - linksElement.show(); - } else { - linksElement.hide(); - } - } - // ************************************************************************* - // private methods - - /** - * Select the tree item corresponding to the indexed bone. - * - * @param boneIndex the index of the bone to select (≥0) - */ - private void selectItem(int boneIndex) { - assert boneIndex >= 0 : boneIndex; - - List> itemList = treeBox.getItems(); - for (TreeItem item : itemList) { - BoneValue value = item.getValue(); - if (value.boneIndex() == boneIndex) { - treeBox.selectItem(item); - break; - } - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.wizard; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.controls.TreeBox; +import de.lessvoid.nifty.controls.TreeItem; +import de.lessvoid.nifty.elements.Element; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.InitialState; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "torso" screen of DacWizard. + * + * @author Stephen Gold sgold@sonic.net + */ +class TorsoScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(TorsoScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of the GUI button to proceed to the "links" screen + */ + private Element linksElement; + /** + * TreeBox to display bones managed by the torso + */ + private TreeBox treeBox; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + TorsoScreen() { + super("torso", "Interface/Nifty/screens/wizard/torso.xml", + InitialState.Disabled); + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("torso"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time the screen shuts down. + */ + @Override + public void onEndScreen() { + treeBox.clear(); + super.onEndScreen(); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + @SuppressWarnings("unchecked") + public void onStartScreen() { + super.onStartScreen(); + + Button linksButton = getButton("links"); + if (linksButton == null) { + throw new RuntimeException("missing GUI control: linksButton"); + } + this.linksElement = linksButton.getElement(); + + this.treeBox = getScreen().findNiftyControl("skeleton", TreeBox.class); + if (treeBox == null) { + throw new RuntimeException("missing GUI control: skeleton"); + } + + TreeItem rootItem = new TreeItem<>(); + rootItem.setExpanded(true); + Model model = DacWizard.getModel(); + int[] managedBones = model.listTorsoManagedBones(); + int numManaged = managedBones.length; + + // Create an item for each bone managed by the torso. + TreeItem[] boneItems = new TreeItem[numManaged]; + for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) { + int boneIndex = managedBones[managedIndex]; + BoneValue value = new BoneValue(boneIndex); + boneItems[managedIndex] = new TreeItem<>(value); + boneItems[managedIndex].setExpanded(true); + } + + // Generate a map from bone indices to managed-bone indices. + int numBones = model.countBones(); + int[] mbiArray = new int[numBones]; + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + mbiArray[boneIndex] = -1; + } + for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) { + int boneIndex = managedBones[managedIndex]; + mbiArray[boneIndex] = managedIndex; + } + + // Parent each item. + for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) { + TreeItem childItem = boneItems[managedIndex]; + int boneIndex = managedBones[managedIndex]; + int parentIndex = model.parentIndex(boneIndex); + if (parentIndex == -1) { + rootItem.addTreeItem(childItem); + } else { + int parentMbi = mbiArray[parentIndex]; + boneItems[parentMbi].addTreeItem(childItem); + } + } + treeBox.setTree(rootItem); + + // Pre-select the item for the main bone. + int mainBoneIndex = model.mainBoneIndex(); + selectItem(mainBoneIndex); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + Model model = DacWizard.getModel(); + List> selectedItems = treeBox.getSelection(); + int numSelected = selectedItems.size(); + if (numSelected == 1) { // Update the main-bone index in the model. + TreeItem mbItem = selectedItems.get(0); + BoneValue mbValue = mbItem.getValue(); + int mbIndex = mbValue.boneIndex(); + model.setMainBoneIndex(mbIndex); + + } else { // empty selection: re-select the main bone + int mainBoneIndex = model.mainBoneIndex(); + selectItem(mainBoneIndex); + } + /* + * If there's a ragdoll already configured, allow the user to + * bypass range-of-motion estimation. + */ + boolean hasRagdoll = model.hasConfiguredRagdoll(); + if (hasRagdoll) { + linksElement.show(); + } else { + linksElement.hide(); + } + } + // ************************************************************************* + // private methods + + /** + * Select the tree item corresponding to the indexed bone. + * + * @param boneIndex the index of the bone to select (≥0) + */ + private void selectItem(int boneIndex) { + assert boneIndex >= 0 : boneIndex; + + List> itemList = treeBox.getItems(); + for (TreeItem item : itemList) { + BoneValue value = item.getValue(); + if (value.boneIndex() == boneIndex) { + treeBox.selectItem(item); + break; + } + } + } +} diff --git a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/bones.xml b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/bones.xml index ac32b5b43..f98a1453a 100644 --- a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/bones.xml +++ b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/bones.xml @@ -1,73 +1,73 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/filePath.xml b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/filePath.xml index 684413128..350a2b7c7 100644 --- a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/filePath.xml +++ b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/filePath.xml @@ -1,77 +1,77 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/links.xml b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/links.xml index e4266c534..1cfa0f919 100644 --- a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/links.xml +++ b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/links.xml @@ -1,151 +1,151 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/load.xml b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/load.xml index 5822af4af..327adcefd 100644 --- a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/load.xml +++ b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/load.xml @@ -1,130 +1,130 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/test.xml b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/test.xml index 6f17c8888..c4343735d 100644 --- a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/test.xml +++ b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/test.xml @@ -1,138 +1,138 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/torso.xml b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/torso.xml index ecc306908..ea6808e3a 100644 --- a/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/torso.xml +++ b/DacWizard/src/main/resources/Interface/Nifty/screens/wizard/torso.xml @@ -1,69 +1,69 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DacWizard/src/main/resources/Textures/icons/license.txt b/DacWizard/src/main/resources/Textures/icons/license.txt index e2cc9eeae..06e4cd8ba 100644 --- a/DacWizard/src/main/resources/Textures/icons/license.txt +++ b/DacWizard/src/main/resources/Textures/icons/license.txt @@ -1,21 +1,21 @@ -Licensing history for assets in src/main/resources/Textures/icons - -Stephen Gold authored "ellipsis.png", "folder.png", and "jme.png". -He makes them available under the Creative Commons -Attribution-Share Alike 3.0 Unported license -- see -https://creativecommons.org/licenses/by-sa/3.0/ for details. - -When sharing or reusing these files, please attribute them to "Stephen Gold's -Minie Project at https://github.com/stephengold/Minie" - -It is easier to ask permission than forgiveness. To request a custom license -granting rights not included in the CC-BY-SA terms, send an e-mail to -sgold@sonic.net with "custom license request" in the subject. Please -clearly indicate: - (1) your legal name - (2) which assets you want to license - (3) name and brief description of your project, if applicable - (4) the licensee (person or organization that will hold the custom license) - (5) what rights you are requesting (i.e. "non-exclusive and non-transferable - perpetual license to incorporate into proprietary software with worldwide +Licensing history for assets in src/main/resources/Textures/icons + +Stephen Gold authored "ellipsis.png", "folder.png", and "jme.png". +He makes them available under the Creative Commons +Attribution-Share Alike 3.0 Unported license -- see +https://creativecommons.org/licenses/by-sa/3.0/ for details. + +When sharing or reusing these files, please attribute them to "Stephen Gold's +Minie Project at https://github.com/stephengold/Minie" + +It is easier to ask permission than forgiveness. To request a custom license +granting rights not included in the CC-BY-SA terms, send an e-mail to +sgold@sonic.net with "custom license request" in the subject. Please +clearly indicate: + (1) your legal name + (2) which assets you want to license + (3) name and brief description of your project, if applicable + (4) the licensee (person or organization that will hold the custom license) + (5) what rights you are requesting (i.e. "non-exclusive and non-transferable + perpetual license to incorporate into proprietary software with worldwide distribution") \ No newline at end of file diff --git a/Jme3Examples/src/main/java/jme3test/bullet/TestBetterCharacter.java b/Jme3Examples/src/main/java/jme3test/bullet/TestBetterCharacter.java index 25065c484..e06adbfbf 100644 --- a/Jme3Examples/src/main/java/jme3test/bullet/TestBetterCharacter.java +++ b/Jme3Examples/src/main/java/jme3test/bullet/TestBetterCharacter.java @@ -1,297 +1,297 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.control.BetterCharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.material.Material; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.RenderManager; -import com.jme3.scene.CameraNode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.control.CameraControl.ControlDirection; -import com.jme3.scene.shape.Sphere; -import com.jme3.system.AppSettings; - -/** - * A walking physical character followed by a 3rd person camera. (No animation.) - * - * @author normenhansen, zathras - */ -public class TestBetterCharacter extends SimpleApplication implements ActionListener { - - private BulletAppState bulletAppState; - private BetterCharacterControl physicsCharacter; - private Node characterNode; - private CameraNode camNode; - final private Vector3f walkDirection = new Vector3f(0, 0, 0); - final private Vector3f viewDirection = new Vector3f(0, 0, 1); - private boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, - leftRotate = false, rightRotate = false; - final private Vector3f normalGravity = new Vector3f(0, -9.81f, 0); - private Geometry planet; - - public static void main(String[] args) { - TestBetterCharacter app = new TestBetterCharacter(); - AppSettings settings = new AppSettings(true); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - settings.setAudioRenderer(AppSettings.LWJGL_OPENAL); - app.setSettings(settings); - app.start(); - } - - @Override - public void simpleInitApp() { - //setup keyboard mapping - setupKeys(); - - // activate physics - bulletAppState = new BulletAppState() { - @Override - public void prePhysicsTick(PhysicsSpace space, float tpf) { - // Apply radial gravity near the planet, downward gravity elsewhere. - checkPlanetGravity(); - } - }; - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); - - // init a physics test scene - PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace()); - PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); - setupPlanet(); - - // Create a node for the character model - characterNode = new Node("character node"); - characterNode.setLocalTranslation(new Vector3f(4, 5, 2)); - - // Add a character control to the node, so we can add other things and - // control the model rotation. - physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f); - characterNode.addControl(physicsCharacter); - getPhysicsSpace().add(physicsCharacter); - - // Load model, attach to character node - Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); - model.setLocalScale(1.50f); - characterNode.attachChild(model); - - // Add character node to the rootNode - rootNode.attachChild(characterNode); - - cam.setLocation(new Vector3f(10f, 6f, -5f)); - - // Set forward camera node that follows the character, only used when - // view is "locked" - camNode = new CameraNode("CamNode", cam); - camNode.setControlDir(ControlDirection.SpatialToCamera); - camNode.setLocalTranslation(new Vector3f(0, 2, -6)); - Quaternion quat = new Quaternion(); - // These coordinates are local, the camNode is attached to the character node! - quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y); - camNode.setLocalRotation(quat); - characterNode.attachChild(camNode); - // Disable by default, can be enabled via keyboard shortcut - camNode.setEnabled(false); - } - - @Override - public void simpleUpdate(float tpf) { - // Get current forward and left vectors of model by using its rotation - // to rotate the unit vectors - Vector3f modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z); - Vector3f modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X); - - // WalkDirection is global! - // You *can* make your character fly with this. - walkDirection.set(0, 0, 0); - if (leftStrafe) { - walkDirection.addLocal(modelLeftDir.mult(3)); - } else if (rightStrafe) { - walkDirection.addLocal(modelLeftDir.negate().multLocal(3)); - } - if (forward) { - walkDirection.addLocal(modelForwardDir.mult(3)); - } else if (backward) { - walkDirection.addLocal(modelForwardDir.negate().multLocal(3)); - } - physicsCharacter.setWalkDirection(walkDirection); - - // ViewDirection is local to characters physics system! - // The final world rotation depends on the gravity and on the state of - // setApplyPhysicsLocal() - if (leftRotate) { - Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y); - rotateL.multLocal(viewDirection); - } else if (rightRotate) { - Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y); - rotateR.multLocal(viewDirection); - } - physicsCharacter.setViewDirection(viewDirection); - fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround()); - if (!lockView) { - cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y); - } - } - - private void setupPlanet() { - Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); - //immovable sphere with mesh collision shape - Sphere sphere = new Sphere(64, 64, 20); - planet = new Geometry("Sphere", sphere); - planet.setMaterial(material); - planet.setLocalTranslation(30, -15, 30); - planet.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0)); - rootNode.attachChild(planet); - getPhysicsSpace().add(planet); - } - - private void checkPlanetGravity() { - Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation()); - if (planetDist.length() < 24) { - physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f)); - } else { - physicsCharacter.setGravity(normalGravity); - } - } - - private PhysicsSpace getPhysicsSpace() { - return bulletAppState.getPhysicsSpace(); - } - - @Override - public void onAction(String binding, boolean value, float tpf) { - if (binding.equals("Strafe Left")) { - if (value) { - leftStrafe = true; - } else { - leftStrafe = false; - } - } else if (binding.equals("Strafe Right")) { - if (value) { - rightStrafe = true; - } else { - rightStrafe = false; - } - } else if (binding.equals("Rotate Left")) { - if (value) { - leftRotate = true; - } else { - leftRotate = false; - } - } else if (binding.equals("Rotate Right")) { - if (value) { - rightRotate = true; - } else { - rightRotate = false; - } - } else if (binding.equals("Walk Forward")) { - if (value) { - forward = true; - } else { - forward = false; - } - } else if (binding.equals("Walk Backward")) { - if (value) { - backward = true; - } else { - backward = false; - } - } else if (binding.equals("Jump")) { - physicsCharacter.jump(); - } else if (binding.equals("Duck")) { - if (value) { - physicsCharacter.setDucked(true); - } else { - physicsCharacter.setDucked(false); - } - } else if (binding.equals("Lock View")) { - if (value && lockView) { - lockView = false; - } else if (value && !lockView) { - lockView = true; - } - flyCam.setEnabled(!lockView); - camNode.setEnabled(lockView); - } - } - private boolean lockView = false; - - private void setupKeys() { - inputManager.addMapping("Strafe Left", - new KeyTrigger(KeyInput.KEY_U), - new KeyTrigger(KeyInput.KEY_Z)); - inputManager.addMapping("Strafe Right", - new KeyTrigger(KeyInput.KEY_O), - new KeyTrigger(KeyInput.KEY_X)); - inputManager.addMapping("Rotate Left", - new KeyTrigger(KeyInput.KEY_J), - new KeyTrigger(KeyInput.KEY_LEFT)); - inputManager.addMapping("Rotate Right", - new KeyTrigger(KeyInput.KEY_L), - new KeyTrigger(KeyInput.KEY_RIGHT)); - inputManager.addMapping("Walk Forward", - new KeyTrigger(KeyInput.KEY_I), - new KeyTrigger(KeyInput.KEY_UP)); - inputManager.addMapping("Walk Backward", - new KeyTrigger(KeyInput.KEY_K), - new KeyTrigger(KeyInput.KEY_DOWN)); - inputManager.addMapping("Jump", - new KeyTrigger(KeyInput.KEY_F), - new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addMapping("Duck", - new KeyTrigger(KeyInput.KEY_G), - new KeyTrigger(KeyInput.KEY_LSHIFT), - new KeyTrigger(KeyInput.KEY_RSHIFT)); - inputManager.addMapping("Lock View", - new KeyTrigger(KeyInput.KEY_RETURN)); - inputManager.addListener(this, "Strafe Left", "Strafe Right"); - inputManager.addListener(this, "Rotate Left", "Rotate Right"); - inputManager.addListener(this, "Walk Forward", "Walk Backward"); - inputManager.addListener(this, "Jump", "Duck", "Lock View"); - } - - @Override - public void simpleRender(RenderManager rm) { - } -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * A walking physical character followed by a 3rd person camera. (No animation.) + * + * @author normenhansen, zathras + */ +public class TestBetterCharacter extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private BetterCharacterControl physicsCharacter; + private Node characterNode; + private CameraNode camNode; + final private Vector3f walkDirection = new Vector3f(0, 0, 0); + final private Vector3f viewDirection = new Vector3f(0, 0, 1); + private boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + leftRotate = false, rightRotate = false; + final private Vector3f normalGravity = new Vector3f(0, -9.81f, 0); + private Geometry planet; + + public static void main(String[] args) { + TestBetterCharacter app = new TestBetterCharacter(); + AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); + settings.setAudioRenderer(AppSettings.LWJGL_OPENAL); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + //setup keyboard mapping + setupKeys(); + + // activate physics + bulletAppState = new BulletAppState() { + @Override + public void prePhysicsTick(PhysicsSpace space, float tpf) { + // Apply radial gravity near the planet, downward gravity elsewhere. + checkPlanetGravity(); + } + }; + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); + + // init a physics test scene + PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); + setupPlanet(); + + // Create a node for the character model + characterNode = new Node("character node"); + characterNode.setLocalTranslation(new Vector3f(4, 5, 2)); + + // Add a character control to the node, so we can add other things and + // control the model rotation. + physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f); + characterNode.addControl(physicsCharacter); + getPhysicsSpace().add(physicsCharacter); + + // Load model, attach to character node + Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); + model.setLocalScale(1.50f); + characterNode.attachChild(model); + + // Add character node to the rootNode + rootNode.attachChild(characterNode); + + cam.setLocation(new Vector3f(10f, 6f, -5f)); + + // Set forward camera node that follows the character, only used when + // view is "locked" + camNode = new CameraNode("CamNode", cam); + camNode.setControlDir(ControlDirection.SpatialToCamera); + camNode.setLocalTranslation(new Vector3f(0, 2, -6)); + Quaternion quat = new Quaternion(); + // These coordinates are local, the camNode is attached to the character node! + quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y); + camNode.setLocalRotation(quat); + characterNode.attachChild(camNode); + // Disable by default, can be enabled via keyboard shortcut + camNode.setEnabled(false); + } + + @Override + public void simpleUpdate(float tpf) { + // Get current forward and left vectors of model by using its rotation + // to rotate the unit vectors + Vector3f modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z); + Vector3f modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X); + + // WalkDirection is global! + // You *can* make your character fly with this. + walkDirection.set(0, 0, 0); + if (leftStrafe) { + walkDirection.addLocal(modelLeftDir.mult(3)); + } else if (rightStrafe) { + walkDirection.addLocal(modelLeftDir.negate().multLocal(3)); + } + if (forward) { + walkDirection.addLocal(modelForwardDir.mult(3)); + } else if (backward) { + walkDirection.addLocal(modelForwardDir.negate().multLocal(3)); + } + physicsCharacter.setWalkDirection(walkDirection); + + // ViewDirection is local to characters physics system! + // The final world rotation depends on the gravity and on the state of + // setApplyPhysicsLocal() + if (leftRotate) { + Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y); + rotateL.multLocal(viewDirection); + } else if (rightRotate) { + Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y); + rotateR.multLocal(viewDirection); + } + physicsCharacter.setViewDirection(viewDirection); + fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround()); + if (!lockView) { + cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y); + } + } + + private void setupPlanet() { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + //immovable sphere with mesh collision shape + Sphere sphere = new Sphere(64, 64, 20); + planet = new Geometry("Sphere", sphere); + planet.setMaterial(material); + planet.setLocalTranslation(30, -15, 30); + planet.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0)); + rootNode.attachChild(planet); + getPhysicsSpace().add(planet); + } + + private void checkPlanetGravity() { + Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation()); + if (planetDist.length() < 24) { + physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f)); + } else { + physicsCharacter.setGravity(normalGravity); + } + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Strafe Left")) { + if (value) { + leftStrafe = true; + } else { + leftStrafe = false; + } + } else if (binding.equals("Strafe Right")) { + if (value) { + rightStrafe = true; + } else { + rightStrafe = false; + } + } else if (binding.equals("Rotate Left")) { + if (value) { + leftRotate = true; + } else { + leftRotate = false; + } + } else if (binding.equals("Rotate Right")) { + if (value) { + rightRotate = true; + } else { + rightRotate = false; + } + } else if (binding.equals("Walk Forward")) { + if (value) { + forward = true; + } else { + forward = false; + } + } else if (binding.equals("Walk Backward")) { + if (value) { + backward = true; + } else { + backward = false; + } + } else if (binding.equals("Jump")) { + physicsCharacter.jump(); + } else if (binding.equals("Duck")) { + if (value) { + physicsCharacter.setDucked(true); + } else { + physicsCharacter.setDucked(false); + } + } else if (binding.equals("Lock View")) { + if (value && lockView) { + lockView = false; + } else if (value && !lockView) { + lockView = true; + } + flyCam.setEnabled(!lockView); + camNode.setEnabled(lockView); + } + } + private boolean lockView = false; + + private void setupKeys() { + inputManager.addMapping("Strafe Left", + new KeyTrigger(KeyInput.KEY_U), + new KeyTrigger(KeyInput.KEY_Z)); + inputManager.addMapping("Strafe Right", + new KeyTrigger(KeyInput.KEY_O), + new KeyTrigger(KeyInput.KEY_X)); + inputManager.addMapping("Rotate Left", + new KeyTrigger(KeyInput.KEY_J), + new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Rotate Right", + new KeyTrigger(KeyInput.KEY_L), + new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addMapping("Walk Forward", + new KeyTrigger(KeyInput.KEY_I), + new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("Walk Backward", + new KeyTrigger(KeyInput.KEY_K), + new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("Jump", + new KeyTrigger(KeyInput.KEY_F), + new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("Duck", + new KeyTrigger(KeyInput.KEY_G), + new KeyTrigger(KeyInput.KEY_LSHIFT), + new KeyTrigger(KeyInput.KEY_RSHIFT)); + inputManager.addMapping("Lock View", + new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(this, "Strafe Left", "Strafe Right"); + inputManager.addListener(this, "Rotate Left", "Rotate Right"); + inputManager.addListener(this, "Walk Forward", "Walk Backward"); + inputManager.addListener(this, "Jump", "Duck", "Lock View"); + } + + @Override + public void simpleRender(RenderManager rm) { + } +} diff --git a/Jme3Examples/src/main/java/jme3test/bullet/TestBrickWall.java b/Jme3Examples/src/main/java/jme3test/bullet/TestBrickWall.java index d0cbdb2bb..c96582f10 100644 --- a/Jme3Examples/src/main/java/jme3test/bullet/TestBrickWall.java +++ b/Jme3Examples/src/main/java/jme3test/bullet/TestBrickWall.java @@ -1,204 +1,204 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.font.BitmapText; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.material.Material; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Sphere.TextureMode; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.WrapMode; - -/** - * - * @author double1984 - */ -public class TestBrickWall extends SimpleApplication { - - final private static float bLength = 0.48f; - final private static float bWidth = 0.24f; - final private static float bHeight = 0.12f; - private Material mat; - private Material mat2; - private Material mat3; - private static Sphere bullet; - private static Box brick; - - private BulletAppState bulletAppState; - - public static void main(String args[]) { - TestBrickWall f = new TestBrickWall(); - f.start(); - } - - @Override - public void simpleInitApp() { - - bulletAppState = new BulletAppState(); - bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); - stateManager.attach(bulletAppState); - - bullet = new Sphere(32, 32, 0.4f, true, false); - bullet.setTextureMode(TextureMode.Projected); - brick = new Box(bLength, bHeight, bWidth); - brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); - - initMaterial(); - initWall(); - initFloor(); - initCrossHairs(); - this.cam.setLocation(new Vector3f(0, 6f, 6f)); - cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0)); - cam.setFrustumFar(15); - inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addListener(actionListener, "shoot"); - inputManager.addMapping("gc", new KeyTrigger(KeyInput.KEY_X)); - inputManager.addListener(actionListener, "gc"); - - rootNode.setShadowMode(ShadowMode.Off); - } - - private PhysicsSpace getPhysicsSpace() { - return bulletAppState.getPhysicsSpace(); - } - final private ActionListener actionListener = new ActionListener() { - - @Override - public void onAction(String name, boolean keyPressed, float tpf) { - if (name.equals("shoot") && !keyPressed) { - Geometry bulletGeometry = new Geometry("bullet", bullet); - bulletGeometry.setMaterial(mat2); - bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); - bulletGeometry.setLocalTranslation(cam.getLocation()); - - SphereCollisionShape bulletCollisionShape = new SphereCollisionShape(0.4f); - RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); -// RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); - bulletNode.setLinearVelocity(cam.getDirection().mult(25)); - bulletGeometry.addControl(bulletNode); - rootNode.attachChild(bulletGeometry); - getPhysicsSpace().add(bulletNode); - } - if (name.equals("gc") && !keyPressed) { - System.gc(); - } - } - }; - - public void initWall() { - float startX = bLength / 4; - float height = 0; - for (int j = 0; j < 15; j++) { - for (int i = 0; i < 4; i++) { - Vector3f vt = new Vector3f(i * bLength * 2 + startX, bHeight + height, 0); - addBrick(vt); - } - startX = -startX; - height += 2 * bHeight; - } - } - - public void initFloor() { - Box floorBox = new Box(10f, 0.1f, 5f); - floorBox.scaleTextureCoordinates(new Vector2f(3, 6)); - - Geometry floor = new Geometry("floor", floorBox); - floor.setMaterial(mat3); - floor.setShadowMode(ShadowMode.Receive); - floor.setLocalTranslation(0, -0.1f, 0); - floor.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(10f, 0.1f, 5f)), 0)); - this.rootNode.attachChild(floor); - this.getPhysicsSpace().add(floor); - } - - public void initMaterial() { - mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); - key.setGenerateMips(true); - Texture tex = assetManager.loadTexture(key); - mat.setTexture("ColorMap", tex); - - mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); - key2.setGenerateMips(true); - Texture tex2 = assetManager.loadTexture(key2); - mat2.setTexture("ColorMap", tex2); - - mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); - key3.setGenerateMips(true); - Texture tex3 = assetManager.loadTexture(key3); - tex3.setWrap(WrapMode.Repeat); - mat3.setTexture("ColorMap", tex3); - } - - public void addBrick(Vector3f ori) { - - Geometry brickGeometry = new Geometry("brick", brick); - brickGeometry.setMaterial(mat); - brickGeometry.setLocalTranslation(ori); - //for geometry with sphere mesh the physics system automatically uses a sphere collision shape - brickGeometry.addControl(new RigidBodyControl(1.5f)); - brickGeometry.setShadowMode(ShadowMode.CastAndReceive); - brickGeometry.getControl(RigidBodyControl.class).setFriction(0.6f); - this.rootNode.attachChild(brickGeometry); - this.getPhysicsSpace().add(brickGeometry); - } - - protected void initCrossHairs() { - guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont); - ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); - ch.setText("+"); // crosshairs - ch.setLocalTranslation( // center - settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, - settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); - guiNode.attachChild(ch); - } -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * + * @author double1984 + */ +public class TestBrickWall extends SimpleApplication { + + final private static float bLength = 0.48f; + final private static float bWidth = 0.24f; + final private static float bHeight = 0.12f; + private Material mat; + private Material mat2; + private Material mat3; + private static Sphere bullet; + private static Box brick; + + private BulletAppState bulletAppState; + + public static void main(String args[]) { + TestBrickWall f = new TestBrickWall(); + f.start(); + } + + @Override + public void simpleInitApp() { + + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + stateManager.attach(bulletAppState); + + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + brick = new Box(bLength, bHeight, bWidth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + + initMaterial(); + initWall(); + initFloor(); + initCrossHairs(); + this.cam.setLocation(new Vector3f(0, 6f, 6f)); + cam.lookAt(Vector3f.ZERO, new Vector3f(0, 1, 0)); + cam.setFrustumFar(15); + inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + inputManager.addMapping("gc", new KeyTrigger(KeyInput.KEY_X)); + inputManager.addListener(actionListener, "gc"); + + rootNode.setShadowMode(ShadowMode.Off); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + final private ActionListener actionListener = new ActionListener() { + + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("shoot") && !keyPressed) { + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat2); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(cam.getLocation()); + + SphereCollisionShape bulletCollisionShape = new SphereCollisionShape(0.4f); + RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); +// RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); + bulletNode.setLinearVelocity(cam.getDirection().mult(25)); + bulletGeometry.addControl(bulletNode); + rootNode.attachChild(bulletGeometry); + getPhysicsSpace().add(bulletNode); + } + if (name.equals("gc") && !keyPressed) { + System.gc(); + } + } + }; + + public void initWall() { + float startX = bLength / 4; + float height = 0; + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 4; i++) { + Vector3f vt = new Vector3f(i * bLength * 2 + startX, bHeight + height, 0); + addBrick(vt); + } + startX = -startX; + height += 2 * bHeight; + } + } + + public void initFloor() { + Box floorBox = new Box(10f, 0.1f, 5f); + floorBox.scaleTextureCoordinates(new Vector2f(3, 6)); + + Geometry floor = new Geometry("floor", floorBox); + floor.setMaterial(mat3); + floor.setShadowMode(ShadowMode.Receive); + floor.setLocalTranslation(0, -0.1f, 0); + floor.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(10f, 0.1f, 5f)), 0)); + this.rootNode.attachChild(floor); + this.getPhysicsSpace().add(floor); + } + + public void initMaterial() { + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + mat.setTexture("ColorMap", tex); + + mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + mat2.setTexture("ColorMap", tex2); + + mat3 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); + key3.setGenerateMips(true); + Texture tex3 = assetManager.loadTexture(key3); + tex3.setWrap(WrapMode.Repeat); + mat3.setTexture("ColorMap", tex3); + } + + public void addBrick(Vector3f ori) { + + Geometry brickGeometry = new Geometry("brick", brick); + brickGeometry.setMaterial(mat); + brickGeometry.setLocalTranslation(ori); + //for geometry with sphere mesh the physics system automatically uses a sphere collision shape + brickGeometry.addControl(new RigidBodyControl(1.5f)); + brickGeometry.setShadowMode(ShadowMode.CastAndReceive); + brickGeometry.getControl(RigidBodyControl.class).setFriction(0.6f); + this.rootNode.attachChild(brickGeometry); + this.getPhysicsSpace().add(brickGeometry); + } + + protected void initCrossHairs() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } +} diff --git a/Jme3Examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java b/Jme3Examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java index f0f4d7807..2e48e60bf 100644 --- a/Jme3Examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java +++ b/Jme3Examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java @@ -1,212 +1,212 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.control.CharacterControl; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.math.Vector3f; -import com.jme3.renderer.RenderManager; -import com.jme3.scene.CameraNode; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.CameraControl.ControlDirection; - -/** - * A walking physical character followed by a 3rd person camera. (No animation.) - * @author normenhansen, zathras - */ -public class TestPhysicsCharacter extends SimpleApplication implements ActionListener { - - private BulletAppState bulletAppState; - private CharacterControl physicsCharacter; - final private Vector3f walkDirection = new Vector3f(0,0,0); - final private Vector3f viewDirection = new Vector3f(0,0,0); - private boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, - leftRotate = false, rightRotate = false; - - public static void main(String[] args) { - TestPhysicsCharacter app = new TestPhysicsCharacter(); - app.start(); - } - - private void setupKeys() { - inputManager.addMapping("Strafe Left", - new KeyTrigger(KeyInput.KEY_Q), - new KeyTrigger(KeyInput.KEY_Z)); - inputManager.addMapping("Strafe Right", - new KeyTrigger(KeyInput.KEY_E), - new KeyTrigger(KeyInput.KEY_X)); - inputManager.addMapping("Rotate Left", - new KeyTrigger(KeyInput.KEY_A), - new KeyTrigger(KeyInput.KEY_LEFT)); - inputManager.addMapping("Rotate Right", - new KeyTrigger(KeyInput.KEY_D), - new KeyTrigger(KeyInput.KEY_RIGHT)); - inputManager.addMapping("Walk Forward", - new KeyTrigger(KeyInput.KEY_W), - new KeyTrigger(KeyInput.KEY_UP)); - inputManager.addMapping("Walk Backward", - new KeyTrigger(KeyInput.KEY_S), - new KeyTrigger(KeyInput.KEY_DOWN)); - inputManager.addMapping("Jump", - new KeyTrigger(KeyInput.KEY_SPACE), - new KeyTrigger(KeyInput.KEY_RETURN)); - inputManager.addMapping("Shoot", - new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addListener(this, "Strafe Left", "Strafe Right"); - inputManager.addListener(this, "Rotate Left", "Rotate Right"); - inputManager.addListener(this, "Walk Forward", "Walk Backward"); - inputManager.addListener(this, "Jump", "Shoot"); - } - @Override - public void simpleInitApp() { - // activate physics - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // init a physical test scene - PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace()); - setupKeys(); - - // Add a physics character to the world - physicsCharacter = new CharacterControl(new CapsuleCollisionShape(0.5f, 1.8f), .1f); - physicsCharacter.setPhysicsLocation(new Vector3f(0, 1, 0)); - Node characterNode = new Node("character node"); - Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); - model.scale(0.25f); - characterNode.addControl(physicsCharacter); - getPhysicsSpace().add(physicsCharacter); - rootNode.attachChild(characterNode); - characterNode.attachChild(model); - - // set forward camera node that follows the character - CameraNode camNode = new CameraNode("CamNode", cam); - camNode.setControlDir(ControlDirection.SpatialToCamera); - camNode.setLocalTranslation(new Vector3f(0, 1, -5)); - camNode.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y); - characterNode.attachChild(camNode); - - //disable the default 1st-person flyCam (don't forget this!!) - flyCam.setEnabled(false); - - } - - @Override - public void simpleUpdate(float tpf) { - Vector3f camDir = cam.getDirection().mult(0.2f); - Vector3f camLeft = cam.getLeft().mult(0.2f); - camDir.y = 0; - camLeft.y = 0; - viewDirection.set(camDir); - walkDirection.set(0, 0, 0); - if (leftStrafe) { - walkDirection.addLocal(camLeft); - } else - if (rightStrafe) { - walkDirection.addLocal(camLeft.negate()); - } - if (leftRotate) { - viewDirection.addLocal(camLeft.mult(tpf)); - } else - if (rightRotate) { - viewDirection.addLocal(camLeft.mult(tpf).negate()); - } - if (forward) { - walkDirection.addLocal(camDir); - } else - if (backward) { - walkDirection.addLocal(camDir.negate()); - } - physicsCharacter.setWalkDirection(walkDirection); - physicsCharacter.setViewDirection(viewDirection); - } - - @Override - public void onAction(String binding, boolean value, float tpf) { - if (binding.equals("Strafe Left")) { - if (value) { - leftStrafe = true; - } else { - leftStrafe = false; - } - } else if (binding.equals("Strafe Right")) { - if (value) { - rightStrafe = true; - } else { - rightStrafe = false; - } - } else if (binding.equals("Rotate Left")) { - if (value) { - leftRotate = true; - } else { - leftRotate = false; - } - } else if (binding.equals("Rotate Right")) { - if (value) { - rightRotate = true; - } else { - rightRotate = false; - } - } else if (binding.equals("Walk Forward")) { - if (value) { - forward = true; - } else { - forward = false; - } - } else if (binding.equals("Walk Backward")) { - if (value) { - backward = true; - } else { - backward = false; - } - } else if (binding.equals("Jump")) { - physicsCharacter.jump(); - } - } - - private PhysicsSpace getPhysicsSpace() { - return bulletAppState.getPhysicsSpace(); - } - - @Override - public void simpleRender(RenderManager rm) { - //TODO: add render code - } -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.CameraNode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.CameraControl.ControlDirection; + +/** + * A walking physical character followed by a 3rd person camera. (No animation.) + * @author normenhansen, zathras + */ +public class TestPhysicsCharacter extends SimpleApplication implements ActionListener { + + private BulletAppState bulletAppState; + private CharacterControl physicsCharacter; + final private Vector3f walkDirection = new Vector3f(0,0,0); + final private Vector3f viewDirection = new Vector3f(0,0,0); + private boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + leftRotate = false, rightRotate = false; + + public static void main(String[] args) { + TestPhysicsCharacter app = new TestPhysicsCharacter(); + app.start(); + } + + private void setupKeys() { + inputManager.addMapping("Strafe Left", + new KeyTrigger(KeyInput.KEY_Q), + new KeyTrigger(KeyInput.KEY_Z)); + inputManager.addMapping("Strafe Right", + new KeyTrigger(KeyInput.KEY_E), + new KeyTrigger(KeyInput.KEY_X)); + inputManager.addMapping("Rotate Left", + new KeyTrigger(KeyInput.KEY_A), + new KeyTrigger(KeyInput.KEY_LEFT)); + inputManager.addMapping("Rotate Right", + new KeyTrigger(KeyInput.KEY_D), + new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addMapping("Walk Forward", + new KeyTrigger(KeyInput.KEY_W), + new KeyTrigger(KeyInput.KEY_UP)); + inputManager.addMapping("Walk Backward", + new KeyTrigger(KeyInput.KEY_S), + new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("Jump", + new KeyTrigger(KeyInput.KEY_SPACE), + new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("Shoot", + new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "Strafe Left", "Strafe Right"); + inputManager.addListener(this, "Rotate Left", "Rotate Right"); + inputManager.addListener(this, "Walk Forward", "Walk Backward"); + inputManager.addListener(this, "Jump", "Shoot"); + } + @Override + public void simpleInitApp() { + // activate physics + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // init a physical test scene + PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace()); + setupKeys(); + + // Add a physics character to the world + physicsCharacter = new CharacterControl(new CapsuleCollisionShape(0.5f, 1.8f), .1f); + physicsCharacter.setPhysicsLocation(new Vector3f(0, 1, 0)); + Node characterNode = new Node("character node"); + Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + model.scale(0.25f); + characterNode.addControl(physicsCharacter); + getPhysicsSpace().add(physicsCharacter); + rootNode.attachChild(characterNode); + characterNode.attachChild(model); + + // set forward camera node that follows the character + CameraNode camNode = new CameraNode("CamNode", cam); + camNode.setControlDir(ControlDirection.SpatialToCamera); + camNode.setLocalTranslation(new Vector3f(0, 1, -5)); + camNode.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y); + characterNode.attachChild(camNode); + + //disable the default 1st-person flyCam (don't forget this!!) + flyCam.setEnabled(false); + + } + + @Override + public void simpleUpdate(float tpf) { + Vector3f camDir = cam.getDirection().mult(0.2f); + Vector3f camLeft = cam.getLeft().mult(0.2f); + camDir.y = 0; + camLeft.y = 0; + viewDirection.set(camDir); + walkDirection.set(0, 0, 0); + if (leftStrafe) { + walkDirection.addLocal(camLeft); + } else + if (rightStrafe) { + walkDirection.addLocal(camLeft.negate()); + } + if (leftRotate) { + viewDirection.addLocal(camLeft.mult(tpf)); + } else + if (rightRotate) { + viewDirection.addLocal(camLeft.mult(tpf).negate()); + } + if (forward) { + walkDirection.addLocal(camDir); + } else + if (backward) { + walkDirection.addLocal(camDir.negate()); + } + physicsCharacter.setWalkDirection(walkDirection); + physicsCharacter.setViewDirection(viewDirection); + } + + @Override + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("Strafe Left")) { + if (value) { + leftStrafe = true; + } else { + leftStrafe = false; + } + } else if (binding.equals("Strafe Right")) { + if (value) { + rightStrafe = true; + } else { + rightStrafe = false; + } + } else if (binding.equals("Rotate Left")) { + if (value) { + leftRotate = true; + } else { + leftRotate = false; + } + } else if (binding.equals("Rotate Right")) { + if (value) { + rightRotate = true; + } else { + rightRotate = false; + } + } else if (binding.equals("Walk Forward")) { + if (value) { + forward = true; + } else { + forward = false; + } + } else if (binding.equals("Walk Backward")) { + if (value) { + backward = true; + } else { + backward = false; + } + } else if (binding.equals("Jump")) { + physicsCharacter.jump(); + } + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } +} diff --git a/Jme3Examples/src/main/java/jme3test/bullet/TestWalkingChar.java b/Jme3Examples/src/main/java/jme3test/bullet/TestWalkingChar.java index 61f08d25f..5101fddab 100644 --- a/Jme3Examples/src/main/java/jme3test/bullet/TestWalkingChar.java +++ b/Jme3Examples/src/main/java/jme3test/bullet/TestWalkingChar.java @@ -1,460 +1,460 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.anim.AnimComposer; -import com.jme3.anim.Armature; -import com.jme3.anim.ArmatureMask; -import com.jme3.anim.SkinningControl; -import com.jme3.anim.tween.Tween; -import com.jme3.anim.tween.Tweens; -import com.jme3.anim.tween.action.Action; -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionEvent; -import com.jme3.bullet.collision.PhysicsCollisionListener; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.CharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.effect.ParticleEmitter; -import com.jme3.effect.ParticleMesh.Type; -import com.jme3.effect.shapes.EmitterSphereShape; -import com.jme3.input.ChaseCamera; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.*; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.filters.BloomFilter; -import com.jme3.renderer.Camera; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.*; -import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Sphere.TextureMode; -import com.jme3.terrain.geomipmap.TerrainLodControl; -import com.jme3.terrain.geomipmap.TerrainQuad; -import com.jme3.terrain.heightmap.AbstractHeightMap; -import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.WrapMode; -import com.jme3.util.SkyFactory; - -import java.util.ArrayList; -import java.util.List; - -/** - * A walking animated character followed by a 3rd person camera on a terrain with LOD. - * @author normenhansen - */ -public class TestWalkingChar extends SimpleApplication - implements ActionListener, PhysicsCollisionListener { - - private BulletAppState bulletAppState; - //character - private CharacterControl character; - private Node model; - //temp vectors - final private Vector3f walkDirection = new Vector3f(); - //Materials - private Material matBullet; - //animation - private Action standAction; - private Action walkAction; - private AnimComposer composer; - private float airTime = 0; - //camera - private boolean left = false, right = false, up = false, down = false; - //bullet - private Sphere bullet; - private SphereCollisionShape bulletCollisionShape; - //explosion - private ParticleEmitter effect; - //brick wall - private Box brick; - final private float bLength = 0.8f; - final private float bWidth = 0.4f; - final private float bHeight = 0.4f; - - public static void main(String[] args) { - TestWalkingChar app = new TestWalkingChar(); - app.start(); - } - - @Override - public void simpleInitApp() { - bulletAppState = new BulletAppState(); - bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); - stateManager.attach(bulletAppState); - setupKeys(); - prepareBullet(); - prepareEffect(); - createLight(); - createSky(); - createTerrain(); - createWall(); - createCharacter(); - setupChaseCamera(); - setupAnimationController(); - setupFilter(); - } - - private void setupFilter() { - FilterPostProcessor fpp = new FilterPostProcessor(assetManager); - BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects); - fpp.addFilter(bloom); - viewPort.addProcessor(fpp); - } - - private PhysicsSpace getPhysicsSpace() { - return bulletAppState.getPhysicsSpace(); - } - - private void setupKeys() { - inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); - inputManager.addListener(this, "wireframe"); - inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A)); - inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D)); - inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W)); - inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S)); - inputManager.addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_RETURN)); - inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addListener(this, "CharLeft"); - inputManager.addListener(this, "CharRight"); - inputManager.addListener(this, "CharUp"); - inputManager.addListener(this, "CharDown"); - inputManager.addListener(this, "CharSpace"); - inputManager.addListener(this, "CharShoot"); - } - - private void createWall() { - float xOff = -144; - float zOff = -40; - float startpt = bLength / 4 - xOff; - float height = 6.1f; - brick = new Box(bLength, bHeight, bWidth); - brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); - for (int j = 0; j < 15; j++) { - for (int i = 0; i < 4; i++) { - Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, zOff); - addBrick(vt); - } - startpt = -startpt; - height += 1.01f * bHeight; - } - } - - private void addBrick(Vector3f ori) { - Geometry brickGeometry = new Geometry("brick", brick); - brickGeometry.setMaterial(matBullet); - brickGeometry.setLocalTranslation(ori); - brickGeometry.addControl(new RigidBodyControl(1.5f)); - brickGeometry.setShadowMode(ShadowMode.CastAndReceive); - this.rootNode.attachChild(brickGeometry); - this.getPhysicsSpace().add(brickGeometry); - } - - private void prepareBullet() { - bullet = new Sphere(32, 32, 0.4f, true, false); - bullet.setTextureMode(TextureMode.Projected); - bulletCollisionShape = new SphereCollisionShape(0.4f); - matBullet = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - matBullet.setColor("Color", ColorRGBA.Green); - matBullet.setColor("GlowColor", ColorRGBA.Green); - getPhysicsSpace().addCollisionListener(this); - } - - private void prepareEffect() { - int COUNT_FACTOR = 1; - float COUNT_FACTOR_F = 1f; - effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR); - effect.setSelectRandomImage(true); - effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); - effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); - effect.setStartSize(1.3f); - effect.setEndSize(2f); - effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); - effect.setParticlesPerSec(0); - effect.setGravity(0, -5, 0); - effect.setLowLife(.4f); - effect.setHighLife(.5f); - effect.getParticleInfluencer() - .setInitialVelocity(new Vector3f(0, 7, 0)); - effect.getParticleInfluencer().setVelocityVariation(1f); - effect.setImagesX(2); - effect.setImagesY(2); - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); - mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); - effect.setMaterial(mat); -// effect.setLocalScale(100); - rootNode.attachChild(effect); - } - - private void createLight() { - Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal(); - DirectionalLight dl = new DirectionalLight(); - dl.setDirection(direction); - dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); - rootNode.addLight(dl); - } - - private void createSky() { - rootNode.attachChild(SkyFactory.createSky(assetManager, - "Textures/Sky/Bright/BrightSky.dds", - SkyFactory.EnvMapType.CubeMap)); - } - - private void createTerrain() { - Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); - matRock.setBoolean("useTriPlanarMapping", false); - matRock.setBoolean("WardIso", true); - matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); - Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); - Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); - grass.setWrap(WrapMode.Repeat); - matRock.setTexture("DiffuseMap", grass); - matRock.setFloat("DiffuseMap_0_scale", 64); - Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); - dirt.setWrap(WrapMode.Repeat); - matRock.setTexture("DiffuseMap_1", dirt); - matRock.setFloat("DiffuseMap_1_scale", 16); - Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); - rock.setWrap(WrapMode.Repeat); - matRock.setTexture("DiffuseMap_2", rock); - matRock.setFloat("DiffuseMap_2_scale", 128); - Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); - normalMap0.setWrap(WrapMode.Repeat); - Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); - normalMap1.setWrap(WrapMode.Repeat); - Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); - normalMap2.setWrap(WrapMode.Repeat); - matRock.setTexture("NormalMap", normalMap0); - matRock.setTexture("NormalMap_1", normalMap1); - matRock.setTexture("NormalMap_2", normalMap2); - - AbstractHeightMap heightmap = null; - try { - heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); - heightmap.load(); - - } catch (Exception e) { - e.printStackTrace(); - } - - TerrainQuad terrain - = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList<>(); - cameras.add(getCamera()); - TerrainLodControl control = new TerrainLodControl(terrain, cameras); - terrain.addControl(control); - terrain.setMaterial(matRock); - terrain.setLocalScale(new Vector3f(2, 2, 2)); - - RigidBodyControl terrainPhysicsNode - = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0); - terrain.addControl(terrainPhysicsNode); - rootNode.attachChild(terrain); - getPhysicsSpace().add(terrainPhysicsNode); - } - - private void createCharacter() { - CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f); - character = new CharacterControl(capsule, 0.01f); - model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - model.addControl(character); - character.setPhysicsLocation(new Vector3f(-140, 40, -10)); - rootNode.attachChild(model); - getPhysicsSpace().add(character); - } - - private void setupChaseCamera() { - flyCam.setEnabled(false); - new ChaseCamera(cam, model, inputManager); - } - - private void setupAnimationController() { - composer = model.getControl(AnimComposer.class); - standAction = composer.action("stand"); - walkAction = composer.action("Walk"); - /* - * Add a "shootOnce" animation action - * that performs the "Dodge" action one time only. - */ - Action dodgeAction = composer.action("Dodge"); - Tween doneTween = Tweens.callMethod(this, "onShootDone"); - composer.actionSequence("shootOnce", dodgeAction, doneTween); - /* - * Define a shooting animation layer - * that animates only the joints of the right arm. - */ - SkinningControl skinningControl - = model.getControl(SkinningControl.class); - Armature armature = skinningControl.getArmature(); - ArmatureMask shootingMask - = ArmatureMask.createMask(armature, "uparm.right"); - composer.makeLayer("shootingLayer", shootingMask); - /* - * Define a walking animation layer - * that animates all joints except those used for shooting. - */ - ArmatureMask walkingMask = new ArmatureMask(); - walkingMask.addBones(armature, "head", "spine", "spinehigh"); - walkingMask.addFromJoint(armature, "hip.left"); - walkingMask.addFromJoint(armature, "hip.right"); - walkingMask.addFromJoint(armature, "uparm.left"); - composer.makeLayer("walkingLayer", walkingMask); - - composer.setCurrentAction("stand", "shootingLayer"); - composer.setCurrentAction("stand", "walkingLayer"); - } - - @Override - public void simpleUpdate(float tpf) { - Vector3f camDir = cam.getDirection().clone().multLocal(0.1f); - Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f); - camDir.y = 0; - camLeft.y = 0; - walkDirection.set(0, 0, 0); - if (left) { - walkDirection.addLocal(camLeft); - } - if (right) { - walkDirection.addLocal(camLeft.negate()); - } - if (up) { - walkDirection.addLocal(camDir); - } - if (down) { - walkDirection.addLocal(camDir.negate()); - } - if (!character.onGround()) { - airTime = airTime + tpf; - } else { - airTime = 0; - } - - Action action = composer.getCurrentAction("walkingLayer"); - if (walkDirection.length() == 0f) { - if (action != standAction) { - composer.setCurrentAction("stand", "walkingLayer"); - } - } else { - character.setViewDirection(walkDirection); - if (airTime > 0.3f) { - if (action != standAction) { - composer.setCurrentAction("stand", "walkingLayer"); - } - } else if (action != walkAction) { - composer.setCurrentAction("Walk", "walkingLayer"); - } - } - character.setWalkDirection(walkDirection); - } - - @Override - public void onAction(String binding, boolean value, float tpf) { - if (binding.equals("CharLeft")) { - if (value) { - left = true; - } else { - left = false; - } - } else if (binding.equals("CharRight")) { - if (value) { - right = true; - } else { - right = false; - } - } else if (binding.equals("CharUp")) { - if (value) { - up = true; - } else { - up = false; - } - } else if (binding.equals("CharDown")) { - if (value) { - down = true; - } else { - down = false; - } - } else if (binding.equals("CharSpace")) { - character.jump(); - } else if (binding.equals("CharShoot") && !value) { - bulletControl(); - } - } - - private void bulletControl() { - composer.setCurrentAction("shootOnce", "shootingLayer"); - - Geometry bulletGeometry = new Geometry("bullet", bullet); - bulletGeometry.setMaterial(matBullet); - bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); - bulletGeometry.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5))); - RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1); - bulletControl.setCcdMotionThreshold(0.1f); - bulletControl.setLinearVelocity(cam.getDirection().mult(80)); - bulletGeometry.addControl(bulletControl); - rootNode.attachChild(bulletGeometry); - getPhysicsSpace().add(bulletControl); - } - - @Override - public void collision(PhysicsCollisionEvent event) { - if (event.getObjectA() instanceof BombControl) { - final Spatial node = event.getNodeA(); - effect.killAllParticles(); - effect.setLocalTranslation(node.getLocalTranslation()); - effect.emitAllParticles(); - } else if (event.getObjectB() instanceof BombControl) { - final Spatial node = event.getNodeB(); - effect.killAllParticles(); - effect.setLocalTranslation(node.getLocalTranslation()); - effect.emitAllParticles(); - } - } - - /** - * Callback to indicate that the "shootOnce" animation action has completed. - */ - void onShootDone() { - /* - * Play the "stand" animation action on the shooting layer. - */ - composer.setCurrentAction("stand", "shootingLayer"); - } -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.Armature; +import com.jme3.anim.ArmatureMask; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.Action; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.*; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.SkyFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * A walking animated character followed by a 3rd person camera on a terrain with LOD. + * @author normenhansen + */ +public class TestWalkingChar extends SimpleApplication + implements ActionListener, PhysicsCollisionListener { + + private BulletAppState bulletAppState; + //character + private CharacterControl character; + private Node model; + //temp vectors + final private Vector3f walkDirection = new Vector3f(); + //Materials + private Material matBullet; + //animation + private Action standAction; + private Action walkAction; + private AnimComposer composer; + private float airTime = 0; + //camera + private boolean left = false, right = false, up = false, down = false; + //bullet + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; + //explosion + private ParticleEmitter effect; + //brick wall + private Box brick; + final private float bLength = 0.8f; + final private float bWidth = 0.4f; + final private float bHeight = 0.4f; + + public static void main(String[] args) { + TestWalkingChar app = new TestWalkingChar(); + app.start(); + } + + @Override + public void simpleInitApp() { + bulletAppState = new BulletAppState(); + bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); + stateManager.attach(bulletAppState); + setupKeys(); + prepareBullet(); + prepareEffect(); + createLight(); + createSky(); + createTerrain(); + createWall(); + createCharacter(); + setupChaseCamera(); + setupAnimationController(); + setupFilter(); + } + + private void setupFilter() { + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + BloomFilter bloom = new BloomFilter(BloomFilter.GlowMode.Objects); + fpp.addFilter(bloom); + viewPort.addProcessor(fpp); + } + + private PhysicsSpace getPhysicsSpace() { + return bulletAppState.getPhysicsSpace(); + } + + private void setupKeys() { + inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addListener(this, "wireframe"); + inputManager.addMapping("CharLeft", new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("CharRight", new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("CharUp", new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("CharDown", new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("CharSpace", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("CharShoot", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "CharLeft"); + inputManager.addListener(this, "CharRight"); + inputManager.addListener(this, "CharUp"); + inputManager.addListener(this, "CharDown"); + inputManager.addListener(this, "CharSpace"); + inputManager.addListener(this, "CharShoot"); + } + + private void createWall() { + float xOff = -144; + float zOff = -40; + float startpt = bLength / 4 - xOff; + float height = 6.1f; + brick = new Box(bLength, bHeight, bWidth); + brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 4; i++) { + Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, zOff); + addBrick(vt); + } + startpt = -startpt; + height += 1.01f * bHeight; + } + } + + private void addBrick(Vector3f ori) { + Geometry brickGeometry = new Geometry("brick", brick); + brickGeometry.setMaterial(matBullet); + brickGeometry.setLocalTranslation(ori); + brickGeometry.addControl(new RigidBodyControl(1.5f)); + brickGeometry.setShadowMode(ShadowMode.CastAndReceive); + this.rootNode.attachChild(brickGeometry); + this.getPhysicsSpace().add(brickGeometry); + } + + private void prepareBullet() { + bullet = new Sphere(32, 32, 0.4f, true, false); + bullet.setTextureMode(TextureMode.Projected); + bulletCollisionShape = new SphereCollisionShape(0.4f); + matBullet = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + matBullet.setColor("Color", ColorRGBA.Green); + matBullet.setColor("GlowColor", ColorRGBA.Green); + getPhysicsSpace().addCollisionListener(this); + } + + private void prepareEffect() { + int COUNT_FACTOR = 1; + float COUNT_FACTOR_F = 1f; + effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR); + effect.setSelectRandomImage(true); + effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); + effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); + effect.setStartSize(1.3f); + effect.setEndSize(2f); + effect.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + effect.setParticlesPerSec(0); + effect.setGravity(0, -5, 0); + effect.setLowLife(.4f); + effect.setHighLife(.5f); + effect.getParticleInfluencer() + .setInitialVelocity(new Vector3f(0, 7, 0)); + effect.getParticleInfluencer().setVelocityVariation(1f); + effect.setImagesX(2); + effect.setImagesY(2); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + effect.setMaterial(mat); +// effect.setLocalScale(100); + rootNode.attachChild(effect); + } + + private void createLight() { + Vector3f direction = new Vector3f(-0.1f, -0.7f, -1).normalizeLocal(); + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(direction); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); + rootNode.addLight(dl); + } + + private void createSky() { + rootNode.attachChild(SkyFactory.createSky(assetManager, + "Textures/Sky/Bright/BrightSky.dds", + SkyFactory.EnvMapType.CubeMap)); + } + + private void createTerrain() { + Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap1); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + + } catch (Exception e) { + e.printStackTrace(); + } + + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList<>(); + cameras.add(getCamera()); + TerrainLodControl control = new TerrainLodControl(terrain, cameras); + terrain.addControl(control); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(2, 2, 2)); + + RigidBodyControl terrainPhysicsNode + = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0); + terrain.addControl(terrainPhysicsNode); + rootNode.attachChild(terrain); + getPhysicsSpace().add(terrainPhysicsNode); + } + + private void createCharacter() { + CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f); + character = new CharacterControl(capsule, 0.01f); + model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + model.addControl(character); + character.setPhysicsLocation(new Vector3f(-140, 40, -10)); + rootNode.attachChild(model); + getPhysicsSpace().add(character); + } + + private void setupChaseCamera() { + flyCam.setEnabled(false); + new ChaseCamera(cam, model, inputManager); + } + + private void setupAnimationController() { + composer = model.getControl(AnimComposer.class); + standAction = composer.action("stand"); + walkAction = composer.action("Walk"); + /* + * Add a "shootOnce" animation action + * that performs the "Dodge" action one time only. + */ + Action dodgeAction = composer.action("Dodge"); + Tween doneTween = Tweens.callMethod(this, "onShootDone"); + composer.actionSequence("shootOnce", dodgeAction, doneTween); + /* + * Define a shooting animation layer + * that animates only the joints of the right arm. + */ + SkinningControl skinningControl + = model.getControl(SkinningControl.class); + Armature armature = skinningControl.getArmature(); + ArmatureMask shootingMask + = ArmatureMask.createMask(armature, "uparm.right"); + composer.makeLayer("shootingLayer", shootingMask); + /* + * Define a walking animation layer + * that animates all joints except those used for shooting. + */ + ArmatureMask walkingMask = new ArmatureMask(); + walkingMask.addBones(armature, "head", "spine", "spinehigh"); + walkingMask.addFromJoint(armature, "hip.left"); + walkingMask.addFromJoint(armature, "hip.right"); + walkingMask.addFromJoint(armature, "uparm.left"); + composer.makeLayer("walkingLayer", walkingMask); + + composer.setCurrentAction("stand", "shootingLayer"); + composer.setCurrentAction("stand", "walkingLayer"); + } + + @Override + public void simpleUpdate(float tpf) { + Vector3f camDir = cam.getDirection().clone().multLocal(0.1f); + Vector3f camLeft = cam.getLeft().clone().multLocal(0.1f); + camDir.y = 0; + camLeft.y = 0; + walkDirection.set(0, 0, 0); + if (left) { + walkDirection.addLocal(camLeft); + } + if (right) { + walkDirection.addLocal(camLeft.negate()); + } + if (up) { + walkDirection.addLocal(camDir); + } + if (down) { + walkDirection.addLocal(camDir.negate()); + } + if (!character.onGround()) { + airTime = airTime + tpf; + } else { + airTime = 0; + } + + Action action = composer.getCurrentAction("walkingLayer"); + if (walkDirection.length() == 0f) { + if (action != standAction) { + composer.setCurrentAction("stand", "walkingLayer"); + } + } else { + character.setViewDirection(walkDirection); + if (airTime > 0.3f) { + if (action != standAction) { + composer.setCurrentAction("stand", "walkingLayer"); + } + } else if (action != walkAction) { + composer.setCurrentAction("Walk", "walkingLayer"); + } + } + character.setWalkDirection(walkDirection); + } + + @Override + public void onAction(String binding, boolean value, float tpf) { + if (binding.equals("CharLeft")) { + if (value) { + left = true; + } else { + left = false; + } + } else if (binding.equals("CharRight")) { + if (value) { + right = true; + } else { + right = false; + } + } else if (binding.equals("CharUp")) { + if (value) { + up = true; + } else { + up = false; + } + } else if (binding.equals("CharDown")) { + if (value) { + down = true; + } else { + down = false; + } + } else if (binding.equals("CharSpace")) { + character.jump(); + } else if (binding.equals("CharShoot") && !value) { + bulletControl(); + } + } + + private void bulletControl() { + composer.setCurrentAction("shootOnce", "shootingLayer"); + + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(matBullet); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5))); + RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1); + bulletControl.setCcdMotionThreshold(0.1f); + bulletControl.setLinearVelocity(cam.getDirection().mult(80)); + bulletGeometry.addControl(bulletControl); + rootNode.attachChild(bulletGeometry); + getPhysicsSpace().add(bulletControl); + } + + @Override + public void collision(PhysicsCollisionEvent event) { + if (event.getObjectA() instanceof BombControl) { + final Spatial node = event.getNodeA(); + effect.killAllParticles(); + effect.setLocalTranslation(node.getLocalTranslation()); + effect.emitAllParticles(); + } else if (event.getObjectB() instanceof BombControl) { + final Spatial node = event.getNodeB(); + effect.killAllParticles(); + effect.setLocalTranslation(node.getLocalTranslation()); + effect.emitAllParticles(); + } + } + + /** + * Callback to indicate that the "shootOnce" animation action has completed. + */ + void onShootDone() { + /* + * Play the "stand" animation action on the shooting layer. + */ + composer.setCurrentAction("stand", "shootingLayer"); + } +} diff --git a/Jme3Examples/src/main/java/jme3test/games/RollingTheMonkey.java b/Jme3Examples/src/main/java/jme3test/games/RollingTheMonkey.java index fe47ad43c..5b26b4a8d 100644 --- a/Jme3Examples/src/main/java/jme3test/games/RollingTheMonkey.java +++ b/Jme3Examples/src/main/java/jme3test/games/RollingTheMonkey.java @@ -1,411 +1,411 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.games; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionEvent; -import com.jme3.bullet.collision.PhysicsCollisionListener; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.GhostControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.font.BitmapText; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.post.FilterPostProcessor; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.shadow.DirectionalLightShadowFilter; -import java.util.concurrent.Callable; - -/** - * Physics based marble game. - * - * @author SkidRunner (Mark E. Picknell) - */ -public class RollingTheMonkey extends SimpleApplication implements ActionListener, PhysicsCollisionListener { - - private static final String MESSAGE = "Thanks for Playing!"; - private static final String INFO_MESSAGE = "Collect all the spinning cubes!\nPress the 'R' key any time to reset!"; - - private static final float PLAYER_DENSITY = 1200; // OLK(Java LOL) = 1200, STEEL = 8000, RUBBER = 1000 - private static final float PLAYER_REST = 0.1f; // OLK = 0.1f, STEEL = 0.0f, RUBBER = 1.0f I made these up. - - private static final float PLAYER_RADIUS = 2.0f; - private static final float PLAYER_ACCEL = 1.0f; - - private static final float PICKUP_SIZE = 0.5f; - private static final float PICKUP_RADIUS = 15.0f; - private static final int PICKUP_COUNT = 16; - private static final float PICKUP_SPEED = 5.0f; - - private static final float PLAYER_VOLUME = (FastMath.pow(PLAYER_RADIUS, 3) * FastMath.PI) / 3; // V = 4/3 * PI * R pow 3 - private static final float PLAYER_MASS = PLAYER_DENSITY * PLAYER_VOLUME; - private static final float PLAYER_FORCE = 80000 * PLAYER_ACCEL; // F = M(4m diameter steel ball) * A - private static final Vector3f PLAYER_START = new Vector3f(0.0f, PLAYER_RADIUS * 2, 0.0f); - - private static final String INPUT_MAPPING_FORWARD = "INPUT_MAPPING_FORWARD"; - private static final String INPUT_MAPPING_BACKWARD = "INPUT_MAPPING_BACKWARD"; - private static final String INPUT_MAPPING_LEFT = "INPUT_MAPPING_LEFT"; - private static final String INPUT_MAPPING_RIGHT = "INPUT_MAPPING_RIGHT"; - private static final String INPUT_MAPPING_RESET = "INPUT_MAPPING_RESET"; - - public static void main(String[] args) { - RollingTheMonkey app = new RollingTheMonkey(); - app.start(); - } - - private boolean keyForward; - private boolean keyBackward; - private boolean keyLeft; - private boolean keyRight; - private RigidBodyControl player; - private int score; - - private Node pickUps; - private BitmapText scoreText; - private BitmapText messageText; - - @Override - public void simpleInitApp() { - flyCam.setEnabled(false); - cam.setLocation(new Vector3f(0.0f, 12.0f, 21.0f)); - viewPort.setBackgroundColor(new ColorRGBA(0.2118f, 0.0824f, 0.6549f, 1.0f)); - - // init physics - BulletAppState bulletState = new BulletAppState(); - stateManager.attach(bulletState); - PhysicsSpace space = bulletState.getPhysicsSpace(); - space.addCollisionListener(this); - - // create light - DirectionalLight sun = new DirectionalLight(); - sun.setDirection((new Vector3f(-0.7f, -0.3f, -0.5f)).normalizeLocal()); - System.out.println("Here We Go: " + sun.getDirection()); - sun.setColor(ColorRGBA.White); - rootNode.addLight(sun); - - // create materials - Material materialRed = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - materialRed.setBoolean("UseMaterialColors",true); - materialRed.setBoolean("HardwareShadows", true); - materialRed.setColor("Diffuse", new ColorRGBA(0.9451f, 0.0078f, 0.0314f, 1.0f)); - materialRed.setColor("Specular", ColorRGBA.White); - materialRed.setFloat("Shininess", 64.0f); - - Material materialGreen = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - materialGreen.setBoolean("UseMaterialColors",true); - materialGreen.setBoolean("HardwareShadows", true); - materialGreen.setColor("Diffuse", new ColorRGBA(0.0431f, 0.7725f, 0.0078f, 1.0f)); - materialGreen.setColor("Specular", ColorRGBA.White); - materialGreen.setFloat("Shininess", 64.0f); - - Material logoMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - logoMaterial.setBoolean("UseMaterialColors",true); - logoMaterial.setBoolean("HardwareShadows", true); - logoMaterial.setTexture("DiffuseMap", assetManager.loadTexture("com/jme3/app/Monkey.png")); - logoMaterial.setColor("Diffuse", ColorRGBA.White); - logoMaterial.setColor("Specular", ColorRGBA.White); - logoMaterial.setFloat("Shininess", 32.0f); - - Material materialYellow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - materialYellow.setBoolean("UseMaterialColors",true); - materialYellow.setBoolean("HardwareShadows", true); - materialYellow.setColor("Diffuse", new ColorRGBA(0.9529f, 0.7843f, 0.0078f, 1.0f)); - materialYellow.setColor("Specular", ColorRGBA.White); - materialYellow.setFloat("Shininess", 64.0f); - - // create level spatial - // TODO: create your own level mesh - Node level = new Node("level"); - level.setShadowMode(ShadowMode.CastAndReceive); - - Geometry floor = new Geometry("floor", new Box(22.0f, 0.5f, 22.0f)); - floor.setShadowMode(ShadowMode.Receive); - floor.setLocalTranslation(0.0f, -0.5f, 0.0f); - floor.setMaterial(materialGreen); - - Geometry wallNorth = new Geometry("wallNorth", new Box(22.0f, 2.0f, 0.5f)); - wallNorth.setLocalTranslation(0.0f, 2.0f, 21.5f); - wallNorth.setMaterial(materialRed); - - Geometry wallSouth = new Geometry("wallSouth", new Box(22.0f, 2.0f, 0.5f)); - wallSouth.setLocalTranslation(0.0f, 2.0f, -21.5f); - wallSouth.setMaterial(materialRed); - - Geometry wallEast = new Geometry("wallEast", new Box(0.5f, 2.0f, 21.0f)); - wallEast.setLocalTranslation(-21.5f, 2.0f, 0.0f); - wallEast.setMaterial(materialRed); - - Geometry wallWest = new Geometry("wallWest", new Box(0.5f, 2.0f, 21.0f)); - wallWest.setLocalTranslation(21.5f, 2.0f, 0.0f); - wallWest.setMaterial(materialRed); - - level.attachChild(floor); - level.attachChild(wallNorth); - level.attachChild(wallSouth); - level.attachChild(wallEast); - level.attachChild(wallWest); - - // The easy way: level.addControl(new RigidBodyControl(0)); - - // create level Shape - CompoundCollisionShape levelShape = new CompoundCollisionShape(); - BoxCollisionShape floorShape = new BoxCollisionShape(new Vector3f(22.0f, 0.5f, 22.0f)); - BoxCollisionShape wallNorthShape = new BoxCollisionShape(new Vector3f(22.0f, 2.0f, 0.5f)); - BoxCollisionShape wallSouthShape = new BoxCollisionShape(new Vector3f(22.0f, 2.0f, 0.5f)); - BoxCollisionShape wallEastShape = new BoxCollisionShape(new Vector3f(0.5f, 2.0f, 21.0f)); - BoxCollisionShape wallWestShape = new BoxCollisionShape(new Vector3f(0.5f, 2.0f, 21.0f)); - - levelShape.addChildShape(floorShape, new Vector3f(0.0f, -0.5f, 0.0f)); - levelShape.addChildShape(wallNorthShape, new Vector3f(0.0f, 2.0f, -21.5f)); - levelShape.addChildShape(wallSouthShape, new Vector3f(0.0f, 2.0f, 21.5f)); - levelShape.addChildShape(wallEastShape, new Vector3f(-21.5f, 2.0f, 0.0f)); - levelShape.addChildShape(wallEastShape, new Vector3f(21.5f, 2.0f, 0.0f)); - - level.addControl(new RigidBodyControl(levelShape, 0)); - - rootNode.attachChild(level); - space.addAll(level); - - // create Pickups - // TODO: create your own pickUp mesh - // create single mesh for all pickups - // HINT: think particles. - pickUps = new Node("pickups"); - - Quaternion rotation = new Quaternion(); - Vector3f translation = new Vector3f(0.0f, PICKUP_SIZE * 1.5f, -PICKUP_RADIUS); - int index = 0; - float amount = FastMath.TWO_PI / PICKUP_COUNT; - for(float angle = 0; angle < FastMath.TWO_PI; angle += amount) { - Geometry pickUp = new Geometry("pickUp" + (index++), new Box(PICKUP_SIZE,PICKUP_SIZE, PICKUP_SIZE)); - pickUp.setShadowMode(ShadowMode.CastAndReceive); - pickUp.setMaterial(materialYellow); - pickUp.setLocalRotation(rotation.fromAngles( - FastMath.rand.nextFloat() * FastMath.TWO_PI, - FastMath.rand.nextFloat() * FastMath.TWO_PI, - FastMath.rand.nextFloat() * FastMath.TWO_PI)); - - rotation.fromAngles(0.0f, angle, 0.0f); - rotation.mult(translation, pickUp.getLocalTranslation()); - pickUps.attachChild(pickUp); - - pickUp.addControl(new GhostControl(new SphereCollisionShape(PICKUP_SIZE))); - - - space.addAll(pickUp); - //space.addCollisionListener(pickUpControl); - } - rootNode.attachChild(pickUps); - - // Create player - // TODO: create your own player mesh - Geometry playerGeometry = new Geometry("player", new Sphere(16, 32, PLAYER_RADIUS)); - playerGeometry.setShadowMode(ShadowMode.CastAndReceive); - playerGeometry.setLocalTranslation(PLAYER_START.clone()); - playerGeometry.setMaterial(logoMaterial); - - // Store control for applying forces - // TODO: create your own player control - player = new RigidBodyControl(new SphereCollisionShape(PLAYER_RADIUS), PLAYER_MASS); - player.setRestitution(PLAYER_REST); - - playerGeometry.addControl(player); - - rootNode.attachChild(playerGeometry); - space.addAll(playerGeometry); - - inputManager.addMapping(INPUT_MAPPING_FORWARD, new KeyTrigger(KeyInput.KEY_UP) - , new KeyTrigger(KeyInput.KEY_W)); - inputManager.addMapping(INPUT_MAPPING_BACKWARD, new KeyTrigger(KeyInput.KEY_DOWN) - , new KeyTrigger(KeyInput.KEY_S)); - inputManager.addMapping(INPUT_MAPPING_LEFT, new KeyTrigger(KeyInput.KEY_LEFT) - , new KeyTrigger(KeyInput.KEY_A)); - inputManager.addMapping(INPUT_MAPPING_RIGHT, new KeyTrigger(KeyInput.KEY_RIGHT) - , new KeyTrigger(KeyInput.KEY_D)); - inputManager.addMapping(INPUT_MAPPING_RESET, new KeyTrigger(KeyInput.KEY_R)); - inputManager.addListener(this, INPUT_MAPPING_FORWARD, INPUT_MAPPING_BACKWARD - , INPUT_MAPPING_LEFT, INPUT_MAPPING_RIGHT, INPUT_MAPPING_RESET); - - // init UI - BitmapText infoText = new BitmapText(guiFont); - infoText.setText(INFO_MESSAGE); - guiNode.attachChild(infoText); - - scoreText = new BitmapText(guiFont); - scoreText.setText("Score: 0"); - guiNode.attachChild(scoreText); - - messageText = new BitmapText(guiFont); - messageText.setText(MESSAGE); - messageText.setLocalScale(0.0f); - guiNode.attachChild(messageText); - - infoText.setLocalTranslation(0.0f, cam.getHeight(), 0.0f); - scoreText.setLocalTranslation((cam.getWidth() - scoreText.getLineWidth()) / 2.0f, - scoreText.getLineHeight(), 0.0f); - messageText.setLocalTranslation((cam.getWidth() - messageText.getLineWidth()) / 2.0f, - (cam.getHeight() - messageText.getLineHeight()) / 2, 0.0f); - - // init shadows - FilterPostProcessor processor = new FilterPostProcessor(assetManager); - DirectionalLightShadowFilter filter = new DirectionalLightShadowFilter(assetManager, 2048, 1); - filter.setLight(sun); - processor.addFilter(filter); - viewPort.addProcessor(processor); - - } - - @Override - public void simpleUpdate(float tpf) { - // Update and position the score - scoreText.setText("Score: " + score); - scoreText.setLocalTranslation((cam.getWidth() - scoreText.getLineWidth()) / 2.0f, - scoreText.getLineHeight(), 0.0f); - - // Rotate all the pickups - float pickUpSpeed = PICKUP_SPEED * tpf; - for(Spatial pickUp : pickUps.getChildren()) { - pickUp.rotate(pickUpSpeed, pickUpSpeed, pickUpSpeed); - } - - Vector3f centralForce = new Vector3f(); - - if(keyForward) centralForce.addLocal(cam.getDirection()); - if(keyBackward) centralForce.addLocal(cam.getDirection().negate()); - if(keyLeft) centralForce.addLocal(cam.getLeft()); - if(keyRight) centralForce.addLocal(cam.getLeft().negate()); - - if(!Vector3f.ZERO.equals(centralForce)) { - centralForce.setY(0); // stop ball from pushing down or flying up - centralForce.normalizeLocal(); // normalize force - centralForce.multLocal(PLAYER_FORCE); // scale vector to force - - player.applyCentralForce(centralForce); // apply force to player - } - - cam.lookAt(player.getPhysicsLocation(), Vector3f.UNIT_Y); - } - - @Override - public void onAction(String name, boolean isPressed, float tpf) { - switch(name) { - case INPUT_MAPPING_FORWARD: - keyForward = isPressed; - break; - case INPUT_MAPPING_BACKWARD: - keyBackward = isPressed; - break; - case INPUT_MAPPING_LEFT: - keyLeft = isPressed; - break; - case INPUT_MAPPING_RIGHT: - keyRight = isPressed; - break; - case INPUT_MAPPING_RESET: - enqueue(new Callable() { - @Override - public Void call() { - reset(); - return null; - } - }); - break; - } - } - @Override - public void collision(PhysicsCollisionEvent event) { - Spatial nodeA = event.getNodeA(); - Spatial nodeB = event.getNodeB(); - - String nameA = nodeA == null ? "" : nodeA.getName(); - String nameB = nodeB == null ? "" : nodeB.getName(); - - if(nameA.equals("player") && nameB.startsWith("pickUp")) { - GhostControl pickUpControl = nodeB.getControl(GhostControl.class); - if(pickUpControl != null && pickUpControl.isEnabled()) { - pickUpControl.setEnabled(false); - nodeB.removeFromParent(); - nodeB.setLocalScale(0.0f); - score += 1; - if(score >= PICKUP_COUNT) { - messageText.setLocalScale(1.0f); - } - } - } else if(nameA.startsWith("pickUp") && nameB.equals("player")) { - GhostControl pickUpControl = nodeA.getControl(GhostControl.class); - if(pickUpControl != null && pickUpControl.isEnabled()) { - pickUpControl.setEnabled(false); - nodeA.setLocalScale(0.0f); - score += 1; - if(score >= PICKUP_COUNT) { - messageText.setLocalScale(1.0f); - } - } - } - } - - private void reset() { - // Reset the pickups - for(Spatial pickUp : pickUps.getChildren()) { - GhostControl pickUpControl = pickUp.getControl(GhostControl.class); - if(pickUpControl != null) { - pickUpControl.setEnabled(true); - } - pickUp.setLocalScale(1.0f); - } - // Reset the player - player.setPhysicsLocation(PLAYER_START.clone()); - player.setAngularVelocity(Vector3f.ZERO.clone()); - player.setLinearVelocity(Vector3f.ZERO.clone()); - // Reset the score - score = 0; - // Reset the message - messageText.setLocalScale(0.0f); - } - -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.games; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionEvent; +import com.jme3.bullet.collision.PhysicsCollisionListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.GhostControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.DirectionalLightShadowFilter; +import java.util.concurrent.Callable; + +/** + * Physics based marble game. + * + * @author SkidRunner (Mark E. Picknell) + */ +public class RollingTheMonkey extends SimpleApplication implements ActionListener, PhysicsCollisionListener { + + private static final String MESSAGE = "Thanks for Playing!"; + private static final String INFO_MESSAGE = "Collect all the spinning cubes!\nPress the 'R' key any time to reset!"; + + private static final float PLAYER_DENSITY = 1200; // OLK(Java LOL) = 1200, STEEL = 8000, RUBBER = 1000 + private static final float PLAYER_REST = 0.1f; // OLK = 0.1f, STEEL = 0.0f, RUBBER = 1.0f I made these up. + + private static final float PLAYER_RADIUS = 2.0f; + private static final float PLAYER_ACCEL = 1.0f; + + private static final float PICKUP_SIZE = 0.5f; + private static final float PICKUP_RADIUS = 15.0f; + private static final int PICKUP_COUNT = 16; + private static final float PICKUP_SPEED = 5.0f; + + private static final float PLAYER_VOLUME = (FastMath.pow(PLAYER_RADIUS, 3) * FastMath.PI) / 3; // V = 4/3 * PI * R pow 3 + private static final float PLAYER_MASS = PLAYER_DENSITY * PLAYER_VOLUME; + private static final float PLAYER_FORCE = 80000 * PLAYER_ACCEL; // F = M(4m diameter steel ball) * A + private static final Vector3f PLAYER_START = new Vector3f(0.0f, PLAYER_RADIUS * 2, 0.0f); + + private static final String INPUT_MAPPING_FORWARD = "INPUT_MAPPING_FORWARD"; + private static final String INPUT_MAPPING_BACKWARD = "INPUT_MAPPING_BACKWARD"; + private static final String INPUT_MAPPING_LEFT = "INPUT_MAPPING_LEFT"; + private static final String INPUT_MAPPING_RIGHT = "INPUT_MAPPING_RIGHT"; + private static final String INPUT_MAPPING_RESET = "INPUT_MAPPING_RESET"; + + public static void main(String[] args) { + RollingTheMonkey app = new RollingTheMonkey(); + app.start(); + } + + private boolean keyForward; + private boolean keyBackward; + private boolean keyLeft; + private boolean keyRight; + private RigidBodyControl player; + private int score; + + private Node pickUps; + private BitmapText scoreText; + private BitmapText messageText; + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + cam.setLocation(new Vector3f(0.0f, 12.0f, 21.0f)); + viewPort.setBackgroundColor(new ColorRGBA(0.2118f, 0.0824f, 0.6549f, 1.0f)); + + // init physics + BulletAppState bulletState = new BulletAppState(); + stateManager.attach(bulletState); + PhysicsSpace space = bulletState.getPhysicsSpace(); + space.addCollisionListener(this); + + // create light + DirectionalLight sun = new DirectionalLight(); + sun.setDirection((new Vector3f(-0.7f, -0.3f, -0.5f)).normalizeLocal()); + System.out.println("Here We Go: " + sun.getDirection()); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + + // create materials + Material materialRed = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + materialRed.setBoolean("UseMaterialColors",true); + materialRed.setBoolean("HardwareShadows", true); + materialRed.setColor("Diffuse", new ColorRGBA(0.9451f, 0.0078f, 0.0314f, 1.0f)); + materialRed.setColor("Specular", ColorRGBA.White); + materialRed.setFloat("Shininess", 64.0f); + + Material materialGreen = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + materialGreen.setBoolean("UseMaterialColors",true); + materialGreen.setBoolean("HardwareShadows", true); + materialGreen.setColor("Diffuse", new ColorRGBA(0.0431f, 0.7725f, 0.0078f, 1.0f)); + materialGreen.setColor("Specular", ColorRGBA.White); + materialGreen.setFloat("Shininess", 64.0f); + + Material logoMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + logoMaterial.setBoolean("UseMaterialColors",true); + logoMaterial.setBoolean("HardwareShadows", true); + logoMaterial.setTexture("DiffuseMap", assetManager.loadTexture("com/jme3/app/Monkey.png")); + logoMaterial.setColor("Diffuse", ColorRGBA.White); + logoMaterial.setColor("Specular", ColorRGBA.White); + logoMaterial.setFloat("Shininess", 32.0f); + + Material materialYellow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + materialYellow.setBoolean("UseMaterialColors",true); + materialYellow.setBoolean("HardwareShadows", true); + materialYellow.setColor("Diffuse", new ColorRGBA(0.9529f, 0.7843f, 0.0078f, 1.0f)); + materialYellow.setColor("Specular", ColorRGBA.White); + materialYellow.setFloat("Shininess", 64.0f); + + // create level spatial + // TODO: create your own level mesh + Node level = new Node("level"); + level.setShadowMode(ShadowMode.CastAndReceive); + + Geometry floor = new Geometry("floor", new Box(22.0f, 0.5f, 22.0f)); + floor.setShadowMode(ShadowMode.Receive); + floor.setLocalTranslation(0.0f, -0.5f, 0.0f); + floor.setMaterial(materialGreen); + + Geometry wallNorth = new Geometry("wallNorth", new Box(22.0f, 2.0f, 0.5f)); + wallNorth.setLocalTranslation(0.0f, 2.0f, 21.5f); + wallNorth.setMaterial(materialRed); + + Geometry wallSouth = new Geometry("wallSouth", new Box(22.0f, 2.0f, 0.5f)); + wallSouth.setLocalTranslation(0.0f, 2.0f, -21.5f); + wallSouth.setMaterial(materialRed); + + Geometry wallEast = new Geometry("wallEast", new Box(0.5f, 2.0f, 21.0f)); + wallEast.setLocalTranslation(-21.5f, 2.0f, 0.0f); + wallEast.setMaterial(materialRed); + + Geometry wallWest = new Geometry("wallWest", new Box(0.5f, 2.0f, 21.0f)); + wallWest.setLocalTranslation(21.5f, 2.0f, 0.0f); + wallWest.setMaterial(materialRed); + + level.attachChild(floor); + level.attachChild(wallNorth); + level.attachChild(wallSouth); + level.attachChild(wallEast); + level.attachChild(wallWest); + + // The easy way: level.addControl(new RigidBodyControl(0)); + + // create level Shape + CompoundCollisionShape levelShape = new CompoundCollisionShape(); + BoxCollisionShape floorShape = new BoxCollisionShape(new Vector3f(22.0f, 0.5f, 22.0f)); + BoxCollisionShape wallNorthShape = new BoxCollisionShape(new Vector3f(22.0f, 2.0f, 0.5f)); + BoxCollisionShape wallSouthShape = new BoxCollisionShape(new Vector3f(22.0f, 2.0f, 0.5f)); + BoxCollisionShape wallEastShape = new BoxCollisionShape(new Vector3f(0.5f, 2.0f, 21.0f)); + BoxCollisionShape wallWestShape = new BoxCollisionShape(new Vector3f(0.5f, 2.0f, 21.0f)); + + levelShape.addChildShape(floorShape, new Vector3f(0.0f, -0.5f, 0.0f)); + levelShape.addChildShape(wallNorthShape, new Vector3f(0.0f, 2.0f, -21.5f)); + levelShape.addChildShape(wallSouthShape, new Vector3f(0.0f, 2.0f, 21.5f)); + levelShape.addChildShape(wallEastShape, new Vector3f(-21.5f, 2.0f, 0.0f)); + levelShape.addChildShape(wallEastShape, new Vector3f(21.5f, 2.0f, 0.0f)); + + level.addControl(new RigidBodyControl(levelShape, 0)); + + rootNode.attachChild(level); + space.addAll(level); + + // create Pickups + // TODO: create your own pickUp mesh + // create single mesh for all pickups + // HINT: think particles. + pickUps = new Node("pickups"); + + Quaternion rotation = new Quaternion(); + Vector3f translation = new Vector3f(0.0f, PICKUP_SIZE * 1.5f, -PICKUP_RADIUS); + int index = 0; + float amount = FastMath.TWO_PI / PICKUP_COUNT; + for(float angle = 0; angle < FastMath.TWO_PI; angle += amount) { + Geometry pickUp = new Geometry("pickUp" + (index++), new Box(PICKUP_SIZE,PICKUP_SIZE, PICKUP_SIZE)); + pickUp.setShadowMode(ShadowMode.CastAndReceive); + pickUp.setMaterial(materialYellow); + pickUp.setLocalRotation(rotation.fromAngles( + FastMath.rand.nextFloat() * FastMath.TWO_PI, + FastMath.rand.nextFloat() * FastMath.TWO_PI, + FastMath.rand.nextFloat() * FastMath.TWO_PI)); + + rotation.fromAngles(0.0f, angle, 0.0f); + rotation.mult(translation, pickUp.getLocalTranslation()); + pickUps.attachChild(pickUp); + + pickUp.addControl(new GhostControl(new SphereCollisionShape(PICKUP_SIZE))); + + + space.addAll(pickUp); + //space.addCollisionListener(pickUpControl); + } + rootNode.attachChild(pickUps); + + // Create player + // TODO: create your own player mesh + Geometry playerGeometry = new Geometry("player", new Sphere(16, 32, PLAYER_RADIUS)); + playerGeometry.setShadowMode(ShadowMode.CastAndReceive); + playerGeometry.setLocalTranslation(PLAYER_START.clone()); + playerGeometry.setMaterial(logoMaterial); + + // Store control for applying forces + // TODO: create your own player control + player = new RigidBodyControl(new SphereCollisionShape(PLAYER_RADIUS), PLAYER_MASS); + player.setRestitution(PLAYER_REST); + + playerGeometry.addControl(player); + + rootNode.attachChild(playerGeometry); + space.addAll(playerGeometry); + + inputManager.addMapping(INPUT_MAPPING_FORWARD, new KeyTrigger(KeyInput.KEY_UP) + , new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping(INPUT_MAPPING_BACKWARD, new KeyTrigger(KeyInput.KEY_DOWN) + , new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping(INPUT_MAPPING_LEFT, new KeyTrigger(KeyInput.KEY_LEFT) + , new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping(INPUT_MAPPING_RIGHT, new KeyTrigger(KeyInput.KEY_RIGHT) + , new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping(INPUT_MAPPING_RESET, new KeyTrigger(KeyInput.KEY_R)); + inputManager.addListener(this, INPUT_MAPPING_FORWARD, INPUT_MAPPING_BACKWARD + , INPUT_MAPPING_LEFT, INPUT_MAPPING_RIGHT, INPUT_MAPPING_RESET); + + // init UI + BitmapText infoText = new BitmapText(guiFont); + infoText.setText(INFO_MESSAGE); + guiNode.attachChild(infoText); + + scoreText = new BitmapText(guiFont); + scoreText.setText("Score: 0"); + guiNode.attachChild(scoreText); + + messageText = new BitmapText(guiFont); + messageText.setText(MESSAGE); + messageText.setLocalScale(0.0f); + guiNode.attachChild(messageText); + + infoText.setLocalTranslation(0.0f, cam.getHeight(), 0.0f); + scoreText.setLocalTranslation((cam.getWidth() - scoreText.getLineWidth()) / 2.0f, + scoreText.getLineHeight(), 0.0f); + messageText.setLocalTranslation((cam.getWidth() - messageText.getLineWidth()) / 2.0f, + (cam.getHeight() - messageText.getLineHeight()) / 2, 0.0f); + + // init shadows + FilterPostProcessor processor = new FilterPostProcessor(assetManager); + DirectionalLightShadowFilter filter = new DirectionalLightShadowFilter(assetManager, 2048, 1); + filter.setLight(sun); + processor.addFilter(filter); + viewPort.addProcessor(processor); + + } + + @Override + public void simpleUpdate(float tpf) { + // Update and position the score + scoreText.setText("Score: " + score); + scoreText.setLocalTranslation((cam.getWidth() - scoreText.getLineWidth()) / 2.0f, + scoreText.getLineHeight(), 0.0f); + + // Rotate all the pickups + float pickUpSpeed = PICKUP_SPEED * tpf; + for(Spatial pickUp : pickUps.getChildren()) { + pickUp.rotate(pickUpSpeed, pickUpSpeed, pickUpSpeed); + } + + Vector3f centralForce = new Vector3f(); + + if(keyForward) centralForce.addLocal(cam.getDirection()); + if(keyBackward) centralForce.addLocal(cam.getDirection().negate()); + if(keyLeft) centralForce.addLocal(cam.getLeft()); + if(keyRight) centralForce.addLocal(cam.getLeft().negate()); + + if(!Vector3f.ZERO.equals(centralForce)) { + centralForce.setY(0); // stop ball from pushing down or flying up + centralForce.normalizeLocal(); // normalize force + centralForce.multLocal(PLAYER_FORCE); // scale vector to force + + player.applyCentralForce(centralForce); // apply force to player + } + + cam.lookAt(player.getPhysicsLocation(), Vector3f.UNIT_Y); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + switch(name) { + case INPUT_MAPPING_FORWARD: + keyForward = isPressed; + break; + case INPUT_MAPPING_BACKWARD: + keyBackward = isPressed; + break; + case INPUT_MAPPING_LEFT: + keyLeft = isPressed; + break; + case INPUT_MAPPING_RIGHT: + keyRight = isPressed; + break; + case INPUT_MAPPING_RESET: + enqueue(new Callable() { + @Override + public Void call() { + reset(); + return null; + } + }); + break; + } + } + @Override + public void collision(PhysicsCollisionEvent event) { + Spatial nodeA = event.getNodeA(); + Spatial nodeB = event.getNodeB(); + + String nameA = nodeA == null ? "" : nodeA.getName(); + String nameB = nodeB == null ? "" : nodeB.getName(); + + if(nameA.equals("player") && nameB.startsWith("pickUp")) { + GhostControl pickUpControl = nodeB.getControl(GhostControl.class); + if(pickUpControl != null && pickUpControl.isEnabled()) { + pickUpControl.setEnabled(false); + nodeB.removeFromParent(); + nodeB.setLocalScale(0.0f); + score += 1; + if(score >= PICKUP_COUNT) { + messageText.setLocalScale(1.0f); + } + } + } else if(nameA.startsWith("pickUp") && nameB.equals("player")) { + GhostControl pickUpControl = nodeA.getControl(GhostControl.class); + if(pickUpControl != null && pickUpControl.isEnabled()) { + pickUpControl.setEnabled(false); + nodeA.setLocalScale(0.0f); + score += 1; + if(score >= PICKUP_COUNT) { + messageText.setLocalScale(1.0f); + } + } + } + } + + private void reset() { + // Reset the pickups + for(Spatial pickUp : pickUps.getChildren()) { + GhostControl pickUpControl = pickUp.getControl(GhostControl.class); + if(pickUpControl != null) { + pickUpControl.setEnabled(true); + } + pickUp.setLocalScale(1.0f); + } + // Reset the player + player.setPhysicsLocation(PLAYER_START.clone()); + player.setAngularVelocity(Vector3f.ZERO.clone()); + player.setLinearVelocity(Vector3f.ZERO.clone()); + // Reset the score + score = 0; + // Reset the message + messageText.setLocalScale(0.0f); + } + +} diff --git a/Jme3Examples/src/main/java/jme3test/helloworld/HelloPhysics.java b/Jme3Examples/src/main/java/jme3test/helloworld/HelloPhysics.java index ec566e390..f7470bb29 100644 --- a/Jme3Examples/src/main/java/jme3test/helloworld/HelloPhysics.java +++ b/Jme3Examples/src/main/java/jme3test/helloworld/HelloPhysics.java @@ -1,225 +1,225 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.helloworld; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.font.BitmapText; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.material.Material; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.shape.Box; -import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Sphere.TextureMode; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.WrapMode; - -/** - * Example 12 - how to give objects physical properties, so they bounce and fall. - * @author base code by double1984, updated by zathras - */ -public class HelloPhysics extends SimpleApplication { - - public static void main(String args[]) { - HelloPhysics app = new HelloPhysics(); - app.start(); - } - - /** Prepare the Physics Application State (jBullet) */ - private BulletAppState bulletAppState; - - /** Prepare Materials */ - private Material wall_mat; - private Material stone_mat; - private Material floor_mat; - - /** Prepare geometries for bricks and cannonballs. */ - private static final Box box; - private static final Sphere sphere; - private static final Box floor; - - /** dimensions used for bricks and wall */ - private static final float brickLength = 0.48f; - private static final float brickWidth = 0.24f; - private static final float brickHeight = 0.12f; - - static { - /* Initialize the cannonball geometry */ - sphere = new Sphere(32, 32, 0.4f, true, false); - sphere.setTextureMode(TextureMode.Projected); - /* Initialize the brick geometry */ - box = new Box(brickLength, brickHeight, brickWidth); - box.scaleTextureCoordinates(new Vector2f(1f, .5f)); - /* Initialize the floor geometry */ - floor = new Box(10f, 0.1f, 5f); - floor.scaleTextureCoordinates(new Vector2f(3, 6)); - } - - @Override - public void simpleInitApp() { - /* Set up Physics Game */ - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - /* Configure cam to look at scene */ - cam.setLocation(new Vector3f(0, 4f, 6f)); - cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y); - /* Initialize the scene, materials, inputs, and physics space */ - initInputs(); - initMaterials(); - initWall(); - initFloor(); - initCrossHairs(); - } - - /** Add InputManager action: Left click triggers shooting. */ - private void initInputs() { - inputManager.addMapping("shoot", - new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addListener(actionListener, "shoot"); - } - - /** - * Every time the shoot action is triggered, a new cannonball is produced. - * The ball is set up to fly from the camera position in the camera direction. - */ - final private ActionListener actionListener = new ActionListener() { - @Override - public void onAction(String name, boolean keyPressed, float tpf) { - if (name.equals("shoot") && !keyPressed) { - makeCannonBall(); - } - } - }; - - /** Initialize the materials used in this scene. */ - public void initMaterials() { - wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); - key.setGenerateMips(true); - Texture tex = assetManager.loadTexture(key); - wall_mat.setTexture("ColorMap", tex); - - stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); - key2.setGenerateMips(true); - Texture tex2 = assetManager.loadTexture(key2); - stone_mat.setTexture("ColorMap", tex2); - - floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); - key3.setGenerateMips(true); - Texture tex3 = assetManager.loadTexture(key3); - tex3.setWrap(WrapMode.Repeat); - floor_mat.setTexture("ColorMap", tex3); - } - - /** Make a solid floor and add it to the scene. */ - public void initFloor() { - Geometry floor_geo = new Geometry("Floor", floor); - floor_geo.setMaterial(floor_mat); - floor_geo.setLocalTranslation(0, -0.1f, 0); - this.rootNode.attachChild(floor_geo); - /* Make the floor physical with mass 0.0f! */ - RigidBodyControl floor_phy = new RigidBodyControl(0.0f); - floor_geo.addControl(floor_phy); - bulletAppState.getPhysicsSpace().add(floor_phy); - } - - /** This loop builds a wall out of individual bricks. */ - public void initWall() { - float startX = brickLength / 4; - float height = 0; - for (int j = 0; j < 15; j++) { - for (int i = 0; i < 6; i++) { - Vector3f vt = - new Vector3f(i * brickLength * 2 + startX, brickHeight + height, 0); - makeBrick(vt); - } - startX = -startX; - height += 2 * brickHeight; - } - } - - /** Creates one physical brick. */ - private void makeBrick(Vector3f loc) { - /* Create a brick geometry and attach it to the scene graph. */ - Geometry brick_geo = new Geometry("brick", box); - brick_geo.setMaterial(wall_mat); - rootNode.attachChild(brick_geo); - /* Position the brick geometry. */ - brick_geo.setLocalTranslation(loc); - /* Make brick physical with a mass > 0. */ - RigidBodyControl brick_phy = new RigidBodyControl(2f); - /* Add physical brick to physics space. */ - brick_geo.addControl(brick_phy); - bulletAppState.getPhysicsSpace().add(brick_phy); - } - - /** Creates one physical cannonball. - * By default, the ball is accelerated and flies - * from the camera position in the camera direction.*/ - public void makeCannonBall() { - /* Create a cannonball geometry and attach to scene graph. */ - Geometry ball_geo = new Geometry("cannon ball", sphere); - ball_geo.setMaterial(stone_mat); - rootNode.attachChild(ball_geo); - /* Position the cannonball. */ - ball_geo.setLocalTranslation(cam.getLocation()); - /* Make the ball physical with a mass > 0.0f */ - RigidBodyControl ball_phy = new RigidBodyControl(1f); - /* Add physical ball to physics space. */ - ball_geo.addControl(ball_phy); - bulletAppState.getPhysicsSpace().add(ball_phy); - /* Accelerate the physical ball to shoot it. */ - ball_phy.setLinearVelocity(cam.getDirection().mult(25)); - } - - /** A plus sign used as crosshairs to help the player with aiming.*/ - protected void initCrossHairs() { - setDisplayStatView(false); - //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont); - ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); - ch.setText("+"); // fake crosshairs :) - ch.setLocalTranslation( // center - settings.getWidth() / 2, - settings.getHeight() / 2, 0); - guiNode.attachChild(ch); - } -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.helloworld; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.font.BitmapText; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Sphere.TextureMode; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * Example 12 - how to give objects physical properties, so they bounce and fall. + * @author base code by double1984, updated by zathras + */ +public class HelloPhysics extends SimpleApplication { + + public static void main(String args[]) { + HelloPhysics app = new HelloPhysics(); + app.start(); + } + + /** Prepare the Physics Application State (jBullet) */ + private BulletAppState bulletAppState; + + /** Prepare Materials */ + private Material wall_mat; + private Material stone_mat; + private Material floor_mat; + + /** Prepare geometries for bricks and cannonballs. */ + private static final Box box; + private static final Sphere sphere; + private static final Box floor; + + /** dimensions used for bricks and wall */ + private static final float brickLength = 0.48f; + private static final float brickWidth = 0.24f; + private static final float brickHeight = 0.12f; + + static { + /* Initialize the cannonball geometry */ + sphere = new Sphere(32, 32, 0.4f, true, false); + sphere.setTextureMode(TextureMode.Projected); + /* Initialize the brick geometry */ + box = new Box(brickLength, brickHeight, brickWidth); + box.scaleTextureCoordinates(new Vector2f(1f, .5f)); + /* Initialize the floor geometry */ + floor = new Box(10f, 0.1f, 5f); + floor.scaleTextureCoordinates(new Vector2f(3, 6)); + } + + @Override + public void simpleInitApp() { + /* Set up Physics Game */ + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + /* Configure cam to look at scene */ + cam.setLocation(new Vector3f(0, 4f, 6f)); + cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y); + /* Initialize the scene, materials, inputs, and physics space */ + initInputs(); + initMaterials(); + initWall(); + initFloor(); + initCrossHairs(); + } + + /** Add InputManager action: Left click triggers shooting. */ + private void initInputs() { + inputManager.addMapping("shoot", + new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(actionListener, "shoot"); + } + + /** + * Every time the shoot action is triggered, a new cannonball is produced. + * The ball is set up to fly from the camera position in the camera direction. + */ + final private ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("shoot") && !keyPressed) { + makeCannonBall(); + } + } + }; + + /** Initialize the materials used in this scene. */ + public void initMaterials() { + wall_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg"); + key.setGenerateMips(true); + Texture tex = assetManager.loadTexture(key); + wall_mat.setTexture("ColorMap", tex); + + stone_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG"); + key2.setGenerateMips(true); + Texture tex2 = assetManager.loadTexture(key2); + stone_mat.setTexture("ColorMap", tex2); + + floor_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg"); + key3.setGenerateMips(true); + Texture tex3 = assetManager.loadTexture(key3); + tex3.setWrap(WrapMode.Repeat); + floor_mat.setTexture("ColorMap", tex3); + } + + /** Make a solid floor and add it to the scene. */ + public void initFloor() { + Geometry floor_geo = new Geometry("Floor", floor); + floor_geo.setMaterial(floor_mat); + floor_geo.setLocalTranslation(0, -0.1f, 0); + this.rootNode.attachChild(floor_geo); + /* Make the floor physical with mass 0.0f! */ + RigidBodyControl floor_phy = new RigidBodyControl(0.0f); + floor_geo.addControl(floor_phy); + bulletAppState.getPhysicsSpace().add(floor_phy); + } + + /** This loop builds a wall out of individual bricks. */ + public void initWall() { + float startX = brickLength / 4; + float height = 0; + for (int j = 0; j < 15; j++) { + for (int i = 0; i < 6; i++) { + Vector3f vt = + new Vector3f(i * brickLength * 2 + startX, brickHeight + height, 0); + makeBrick(vt); + } + startX = -startX; + height += 2 * brickHeight; + } + } + + /** Creates one physical brick. */ + private void makeBrick(Vector3f loc) { + /* Create a brick geometry and attach it to the scene graph. */ + Geometry brick_geo = new Geometry("brick", box); + brick_geo.setMaterial(wall_mat); + rootNode.attachChild(brick_geo); + /* Position the brick geometry. */ + brick_geo.setLocalTranslation(loc); + /* Make brick physical with a mass > 0. */ + RigidBodyControl brick_phy = new RigidBodyControl(2f); + /* Add physical brick to physics space. */ + brick_geo.addControl(brick_phy); + bulletAppState.getPhysicsSpace().add(brick_phy); + } + + /** Creates one physical cannonball. + * By default, the ball is accelerated and flies + * from the camera position in the camera direction.*/ + public void makeCannonBall() { + /* Create a cannonball geometry and attach to scene graph. */ + Geometry ball_geo = new Geometry("cannon ball", sphere); + ball_geo.setMaterial(stone_mat); + rootNode.attachChild(ball_geo); + /* Position the cannonball. */ + ball_geo.setLocalTranslation(cam.getLocation()); + /* Make the ball physical with a mass > 0.0f */ + RigidBodyControl ball_phy = new RigidBodyControl(1f); + /* Add physical ball to physics space. */ + ball_geo.addControl(ball_phy); + bulletAppState.getPhysicsSpace().add(ball_phy); + /* Accelerate the physical ball to shoot it. */ + ball_phy.setLinearVelocity(cam.getDirection().mult(25)); + } + + /** A plus sign used as crosshairs to help the player with aiming.*/ + protected void initCrossHairs() { + setDisplayStatView(false); + //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // fake crosshairs :) + ch.setLocalTranslation( // center + settings.getWidth() / 2, + settings.getHeight() / 2, 0); + guiNode.attachChild(ch); + } +} diff --git a/MinieAssets/build.gradle b/MinieAssets/build.gradle index 588065a4c..d1fffee30 100644 --- a/MinieAssets/build.gradle +++ b/MinieAssets/build.gradle @@ -1,197 +1,197 @@ -// Note: "common.gradle" in the root project contains additional initialization -// for this project. This initialization is applied in the "build.gradle" -// of the root project. - -ext { - // output directories - assets = '../MinieExamples/src/main/resources/' - glyphAssets = assets + 'CollisionShapes/glyphs/' - poolBalls = assets + 'Textures/poolBalls/' - - // output directories for C-G models - baseMesh = assets + 'Models/BaseMesh' - candyDish = assets + 'Models/CandyDish' - duck = assets + 'Models/Duck' - elephant = assets + 'Models/Elephant' - jaime = assets + 'Models/Jaime' - mhGame = assets + 'Models/MhGame' - monkeyHead = assets + 'Models/MonkeyHead' - ninja = assets + 'Models/Ninja' - oto = assets + 'Models/Oto' - sinbad = assets + 'Models/Sinbad' - - // generated collision-shape J3O files - ankhShape = assets + 'CollisionShapes/ankh.j3o' - bananaShape = assets + 'CollisionShapes/banana.j3o' - barrelShape = assets + 'CollisionShapes/barrel.j3o' - bowlingPinShape = assets + 'CollisionShapes/bowlingPin.j3o' - duckShape = assets + 'CollisionShapes/duck.j3o' - heartShape = assets + 'CollisionShapes/heart.j3o' - horseshoeShape = assets + 'CollisionShapes/horseshoe.j3o' - swordShape = assets + 'CollisionShapes/sword.j3o' - teapotShape = assets + 'CollisionShapes/teapot.j3o' - teapotGiShape = assets + 'CollisionShapes/teapotGi.j3o' - - // generated texture files - greenTile = assets + 'Textures/greenTile.png' - plaid = assets + 'Textures/plaid.png' -} - -tasks.withType(JavaCompile) { // Java compile-time options: - options.deprecation = true -} - -dependencies { - implementation 'com.github.stephengold:jme-ttf:3.0.0' - implementation heartCoordinates - implementation 'org.jmonkeyengine:jme3-blender:3.3.2-stable' - runtimeOnly 'org.jmonkeyengine:jme3-desktop:' + jme3Version - implementation 'org.jmonkeyengine:jme3-plugins:' + jme3Version - runtimeOnly testdataCoordinates - - // MinieAssets doesn't use jme3-jogg - // -- it is included solely to avoid warnings from AssetConfig. - runtimeOnly 'org.jmonkeyengine:jme3-jogg:' + jme3Version - - //implementation 'com.github.stephengold:Minie:' + minieVersion - implementation project(':MinieLibrary') -} - -tasks.register('models', JavaExec) { - description 'Generates C-G model assets used in MinieExamples.' - mainClass = 'jme3utilities.minie.test.models.ImportCgms' - outputs.files([ - baseMesh + '/BaseMesh.j3o', - candyDish + '/CandyDish.j3o', - duck + '/Duck.j3o', - elephant + '/Elephant.j3o', - jaime + '/Jaime-new.j3o', - mhGame + '/MhGame.j3o', - monkeyHead + '/MonkeyHead.j3o', - ninja + '/Ninja.j3o', - oto + '/Oto.j3o', - sinbad + '/Sinbad.j3o', - sinbad + '/Sword.j3o' - ]) -} - -tasks.register('shapes') { - dependsOn 'ankhShape', 'bananaShape', 'barrelShape', 'bowlingPinShape', \ - 'duckShape', 'glyphShapes', 'heartShape', 'horseshoeShape', \ - 'swordShape', 'teapotShape' - description 'Generates collision-shape assets used in MinieExamples.' -} -tasks.register('ankhShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeAnkh' - outputs.files([ankhShape]) -} -tasks.register('bananaShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeBanana' - outputs.files([bananaShape]) -} -tasks.register('barrelShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeBarrel' - outputs.files([barrelShape]) -} -tasks.register('bowlingPinShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeBowlingPin' - outputs.files([bowlingPinShape]) -} -tasks.register('duckShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeDuck' - outputs.files([duckShape]) -} -tasks.register('glyphShapes', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeGlyphs' - outputs.dirs([glyphAssets]) -} -tasks.register('heartShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeHeart' - outputs.files([heartShape]) -} -tasks.register('horseshoeShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeHorseshoe' - outputs.files([horseshoeShape]) -} -tasks.register('swordShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeSword' - outputs.files([swordShape]) -} -tasks.register('teapotShape', JavaExec) { - mainClass = 'jme3utilities.minie.test.shapes.MakeTeapot' - outputs.files([teapotShape], [teapotGiShape]) -} - -tasks.register('textures') { - dependsOn 'greenTile', 'plaid', 'poolBalls' - description 'Generates texture assets used in MinieExamples.' -} -tasks.register('greenTile', JavaExec) { - mainClass = 'jme3utilities.minie.test.textures.MakeGreenTile' - outputs.files([greenTile]) -} -tasks.register('plaid', JavaExec) { - mainClass = 'jme3utilities.minie.test.textures.MakePlaid' - outputs.files([plaid]) -} -tasks.register('poolBalls', JavaExec) { - mainClass = 'jme3utilities.minie.test.textures.MakePoolBalls' - outputs.dirs([poolBalls]) -} - -// cleanup tasks: - -clean.dependsOn('cleanModels', 'cleanShapes', 'cleanTextures') - -tasks.register('cleanModels', Delete) { - delete(baseMesh, candyDish, duck, elephant, jaime, mhGame, \ - monkeyHead, ninja, sinbad) - dependsOn 'cleanOto' - description 'Deletes generated model assets.' -} -tasks.register('cleanOto', Delete) { - delete fileTree(dir: oto, include: 'Oto.*') -} - -tasks.register('cleanShapes') { - dependsOn 'cleanAnkhShape', 'cleanBananaShape', 'cleanBarrelShape', \ - 'cleanBowlingPinShape', 'cleanDuckShape', 'cleanGlyphShapes', \ - 'cleanHeartShape', 'cleanHorseshoeShape', 'cleanSwordShape', \ - 'cleanTeapotShape' - description 'Deletes generated collision-shape assets.' -} -tasks.register('cleanAnkhShape', Delete) { - delete ankhShape -} -tasks.register('cleanBananaShape', Delete) { - delete bananaShape -} -tasks.register('cleanBarrelShape', Delete) { - delete barrelShape -} -tasks.register('cleanBowlingPinShape', Delete) { - delete bowlingPinShape -} -tasks.register('cleanDuckShape', Delete) { - delete duckShape -} -tasks.register('cleanGlyphShapes', Delete) { - delete glyphAssets -} -tasks.register('cleanHeartShape', Delete) { - delete heartShape -} -tasks.register('cleanHorseshoeShape', Delete) { - delete horseshoeShape -} -tasks.register('cleanSwordShape', Delete) { - delete swordShape -} -tasks.register('cleanTeapotShape', Delete) { - delete teapotShape, teapotGiShape -} - -tasks.register('cleanTextures', Delete) { - delete(greenTile, plaid, poolBalls) - description 'Deletes generated texture assets.' -} +// Note: "common.gradle" in the root project contains additional initialization +// for this project. This initialization is applied in the "build.gradle" +// of the root project. + +ext { + // output directories + assets = '../MinieExamples/src/main/resources/' + glyphAssets = assets + 'CollisionShapes/glyphs/' + poolBalls = assets + 'Textures/poolBalls/' + + // output directories for C-G models + baseMesh = assets + 'Models/BaseMesh' + candyDish = assets + 'Models/CandyDish' + duck = assets + 'Models/Duck' + elephant = assets + 'Models/Elephant' + jaime = assets + 'Models/Jaime' + mhGame = assets + 'Models/MhGame' + monkeyHead = assets + 'Models/MonkeyHead' + ninja = assets + 'Models/Ninja' + oto = assets + 'Models/Oto' + sinbad = assets + 'Models/Sinbad' + + // generated collision-shape J3O files + ankhShape = assets + 'CollisionShapes/ankh.j3o' + bananaShape = assets + 'CollisionShapes/banana.j3o' + barrelShape = assets + 'CollisionShapes/barrel.j3o' + bowlingPinShape = assets + 'CollisionShapes/bowlingPin.j3o' + duckShape = assets + 'CollisionShapes/duck.j3o' + heartShape = assets + 'CollisionShapes/heart.j3o' + horseshoeShape = assets + 'CollisionShapes/horseshoe.j3o' + swordShape = assets + 'CollisionShapes/sword.j3o' + teapotShape = assets + 'CollisionShapes/teapot.j3o' + teapotGiShape = assets + 'CollisionShapes/teapotGi.j3o' + + // generated texture files + greenTile = assets + 'Textures/greenTile.png' + plaid = assets + 'Textures/plaid.png' +} + +tasks.withType(JavaCompile) { // Java compile-time options: + options.deprecation = true +} + +dependencies { + implementation 'com.github.stephengold:jme-ttf:3.0.0' + implementation heartCoordinates + implementation 'org.jmonkeyengine:jme3-blender:3.3.2-stable' + runtimeOnly 'org.jmonkeyengine:jme3-desktop:' + jme3Version + implementation 'org.jmonkeyengine:jme3-plugins:' + jme3Version + runtimeOnly testdataCoordinates + + // MinieAssets doesn't use jme3-jogg + // -- it is included solely to avoid warnings from AssetConfig. + runtimeOnly 'org.jmonkeyengine:jme3-jogg:' + jme3Version + + //implementation 'com.github.stephengold:Minie:' + minieVersion + implementation project(':MinieLibrary') +} + +tasks.register('models', JavaExec) { + description 'Generates C-G model assets used in MinieExamples.' + mainClass = 'jme3utilities.minie.test.models.ImportCgms' + outputs.files([ + baseMesh + '/BaseMesh.j3o', + candyDish + '/CandyDish.j3o', + duck + '/Duck.j3o', + elephant + '/Elephant.j3o', + jaime + '/Jaime-new.j3o', + mhGame + '/MhGame.j3o', + monkeyHead + '/MonkeyHead.j3o', + ninja + '/Ninja.j3o', + oto + '/Oto.j3o', + sinbad + '/Sinbad.j3o', + sinbad + '/Sword.j3o' + ]) +} + +tasks.register('shapes') { + dependsOn 'ankhShape', 'bananaShape', 'barrelShape', 'bowlingPinShape', \ + 'duckShape', 'glyphShapes', 'heartShape', 'horseshoeShape', \ + 'swordShape', 'teapotShape' + description 'Generates collision-shape assets used in MinieExamples.' +} +tasks.register('ankhShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeAnkh' + outputs.files([ankhShape]) +} +tasks.register('bananaShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeBanana' + outputs.files([bananaShape]) +} +tasks.register('barrelShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeBarrel' + outputs.files([barrelShape]) +} +tasks.register('bowlingPinShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeBowlingPin' + outputs.files([bowlingPinShape]) +} +tasks.register('duckShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeDuck' + outputs.files([duckShape]) +} +tasks.register('glyphShapes', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeGlyphs' + outputs.dirs([glyphAssets]) +} +tasks.register('heartShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeHeart' + outputs.files([heartShape]) +} +tasks.register('horseshoeShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeHorseshoe' + outputs.files([horseshoeShape]) +} +tasks.register('swordShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeSword' + outputs.files([swordShape]) +} +tasks.register('teapotShape', JavaExec) { + mainClass = 'jme3utilities.minie.test.shapes.MakeTeapot' + outputs.files([teapotShape], [teapotGiShape]) +} + +tasks.register('textures') { + dependsOn 'greenTile', 'plaid', 'poolBalls' + description 'Generates texture assets used in MinieExamples.' +} +tasks.register('greenTile', JavaExec) { + mainClass = 'jme3utilities.minie.test.textures.MakeGreenTile' + outputs.files([greenTile]) +} +tasks.register('plaid', JavaExec) { + mainClass = 'jme3utilities.minie.test.textures.MakePlaid' + outputs.files([plaid]) +} +tasks.register('poolBalls', JavaExec) { + mainClass = 'jme3utilities.minie.test.textures.MakePoolBalls' + outputs.dirs([poolBalls]) +} + +// cleanup tasks: + +clean.dependsOn('cleanModels', 'cleanShapes', 'cleanTextures') + +tasks.register('cleanModels', Delete) { + delete(baseMesh, candyDish, duck, elephant, jaime, mhGame, \ + monkeyHead, ninja, sinbad) + dependsOn 'cleanOto' + description 'Deletes generated model assets.' +} +tasks.register('cleanOto', Delete) { + delete fileTree(dir: oto, include: 'Oto.*') +} + +tasks.register('cleanShapes') { + dependsOn 'cleanAnkhShape', 'cleanBananaShape', 'cleanBarrelShape', \ + 'cleanBowlingPinShape', 'cleanDuckShape', 'cleanGlyphShapes', \ + 'cleanHeartShape', 'cleanHorseshoeShape', 'cleanSwordShape', \ + 'cleanTeapotShape' + description 'Deletes generated collision-shape assets.' +} +tasks.register('cleanAnkhShape', Delete) { + delete ankhShape +} +tasks.register('cleanBananaShape', Delete) { + delete bananaShape +} +tasks.register('cleanBarrelShape', Delete) { + delete barrelShape +} +tasks.register('cleanBowlingPinShape', Delete) { + delete bowlingPinShape +} +tasks.register('cleanDuckShape', Delete) { + delete duckShape +} +tasks.register('cleanGlyphShapes', Delete) { + delete glyphAssets +} +tasks.register('cleanHeartShape', Delete) { + delete heartShape +} +tasks.register('cleanHorseshoeShape', Delete) { + delete horseshoeShape +} +tasks.register('cleanSwordShape', Delete) { + delete swordShape +} +tasks.register('cleanTeapotShape', Delete) { + delete teapotShape, teapotGiShape +} + +tasks.register('cleanTextures', Delete) { + delete(greenTile, plaid, poolBalls) + description 'Deletes generated texture assets.' +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/models/ImportCgms.java b/MinieAssets/src/main/java/jme3utilities/minie/test/models/ImportCgms.java index f468302a9..1c87146b3 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/models/ImportCgms.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/models/ImportCgms.java @@ -1,292 +1,292 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.models; - -import com.jme3.anim.Armature; -import com.jme3.anim.Joint; -import com.jme3.anim.util.AnimMigrationUtils; -import com.jme3.app.SimpleApplication; -import com.jme3.asset.AssetInfo; -import com.jme3.asset.BlenderKey; -import com.jme3.asset.TextureKey; -import com.jme3.material.MatParam; -import com.jme3.material.Material; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.UserData; -import com.jme3.scene.plugins.blender.BlenderLoader; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.ogre.MaterialLoader; -import com.jme3.scene.plugins.ogre.MeshLoader; -import com.jme3.shader.VarType; -import com.jme3.system.JmeContext; -import com.jme3.texture.Texture; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.imageio.ImageIO; -import jme3utilities.Heart; -import jme3utilities.MySkeleton; -import jme3utilities.MySpatial; -import jme3utilities.MyString; - -/** - * A headless SimpleApplication to import certain C-G models used in the - * MinieExamples sub-project. - * - * @author Stephen Gold sgold@sonic.net - */ -public class ImportCgms extends SimpleApplication { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(ImportCgms.class.getName()); - /** - * filesystem path to the destination asset root (the directory/folder for - * output) - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the ImportCgms application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Instantiate the application. - ImportCgms application = new ImportCgms(); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Import the C-G models. - application.start(JmeContext.Type.Headless); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Convert non-J3O format computer-graphics models to J3O format for faster - * loading. Also write out the textures used by those models. - */ - @Override - public void simpleInitApp() { - Logger.getLogger(MaterialLoader.class.getName()).setLevel(Level.SEVERE); - Logger.getLogger(MeshLoader.class.getName()).setLevel(Level.SEVERE); - Logger.getLogger(TemporalMesh.class.getName()).setLevel(Level.SEVERE); - - // Import the BaseMesh model from jme3-testdata-3.1.0-stable.jar: - assetManager.registerLoader(BlenderLoader.class, "blend"); - BlenderKey blendKey = new BlenderKey("Blender/2.4x/BaseMesh_249.blend"); - Spatial baseMesh = assetManager.loadModel(blendKey); - AnimMigrationUtils.migrate(baseMesh); - - // Ensure that every Joint has a name. - List list = MySkeleton.listArmatures(baseMesh, null); - assert list.size() == 1 : list.size(); - Armature armature = list.get(0); - int numJoints = armature.getJointCount(); - for (int jointIndex = 0; jointIndex < numJoints; ++jointIndex) { - Joint joint = armature.getJoint(jointIndex); - if (joint.getName().isEmpty()) { - joint.setName("bone_" + jointIndex); - } - } - writeToJ3O(baseMesh, "Models/BaseMesh/BaseMesh.j3o"); - /* - * Import the Duck model (by Sony Computer Entertainment Inc.) - * from src/main/resources: - */ - Spatial duck = assetManager.loadModel("Models/Duck/Duck.gltf"); - writeToJ3O(duck, "Models/Duck/Duck.j3o"); - writeTextures(duck); - - // Import the Elephant model from jme3-testdata-3.1.0-stable.jar: - Spatial elephant - = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); - writeToJ3O(elephant, "Models/Elephant/Elephant.j3o"); - writeTextures(elephant); - /* - * Import the Jaime model (by Rémy Bouquet) - * from jme3-testdata-3.1.0-stable.jar: - */ - Spatial jaime = assetManager.loadModel("Models/Jaime/Jaime.j3o"); - AnimMigrationUtils.migrate(jaime); - writeToJ3O(jaime, "Models/Jaime/Jaime-new.j3o"); - /* - * Import the MhGame model (by Stephen Gold) - * from src/main/resources: - */ - Spatial mhGame - = assetManager.loadModel("Models/MhGame/MhGame.mesh.xml"); - writeToJ3O(mhGame, "Models/MhGame/MhGame.j3o"); - writeTextures(mhGame); - - // Import the MonkeyHead model from jme3-testdata-3.1.0-stable.jar: - Spatial monkeyHead = assetManager.loadModel( - "Models/MonkeyHead/MonkeyHead.mesh.xml"); - writeToJ3O(monkeyHead, "Models/MonkeyHead/MonkeyHead.j3o"); - writeTextures(monkeyHead); - - // Import the Ninja model from jme3-testdata-3.1.0-stable.jar: - Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); - writeToJ3O(ninja, "Models/Ninja/Ninja.j3o"); - writeTextures(ninja); - /* - * Import the CandyDish model (by Stephen Gold) - * from src/main/resources: - */ - Spatial candyDish - = assetManager.loadModel("Models/CandyDish/CandyDish.glb"); - writeToJ3O(candyDish, "Models/CandyDish/CandyDish.j3o"); - /* - * Import the Oto model (by OtoTheCleaner) - * from jme3-testdata-3.1.0-stable.jar: - */ - Spatial oto = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - writeToJ3O(oto, "Models/Oto/Oto.j3o"); - writeTextures(oto); - /* - * Import the Sinbad model (by Zi Ye) - * from jme3-testdata-3.1.0-stable.jar: - */ - Node sinbad = (Node) assetManager.loadModel( - "Models/Sinbad/Sinbad.mesh.xml"); - sinbad.getChild(4).setUserData(UserData.JME_PHYSICSIGNORE, true); - writeToJ3O(sinbad, "Models/Sinbad/Sinbad.j3o"); - writeTextures(sinbad); - - Spatial sword = assetManager.loadModel("Models/Sinbad/Sword.mesh.xml"); - writeToJ3O(sword, "Models/Sinbad/Sword.j3o"); - writeTextures(sword); - - stop(); - } - // ************************************************************************* - // private methods - - /** - * Write the image of a texture to JPG or PNG file. - * - * @param key access the texture to be written (not null) - */ - private void writeImage(TextureKey key) { - String suffix = key.getExtension(); - String assetPath = key.getName(); - - AssetInfo info = assetManager.locateAsset(key); - InputStream stream = info.openStream(); - - BufferedImage image; - try { - image = ImageIO.read(stream); - } catch (IOException exception) { - logger.log(Level.SEVERE, "failed to read {0}", - MyString.quote(assetPath)); - throw new RuntimeException(exception); - } - - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - File file = new File(writeFilePath); - - // Create the parent folder. - File parent = file.getParentFile(); - if (parent != null && !parent.exists()) { - boolean success = parent.mkdirs(); - if (!success) { - String message = "mkdirs() failed while saving " - + MyString.quote(writeFilePath); - throw new RuntimeException(message); - } - } - /* - * Write the texture's BufferedImage in the - * format specified by its suffix. - */ - try { - ImageIO.write(image, suffix, file); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - logger.log(Level.INFO, "wrote file {0}", MyString.quote(writeFilePath)); - } - - /** - * Write the image of each 2-D texture used in the specified model. - * - * @param model the scene-graph subtree to analyze (may be null) - */ - private void writeTextures(Spatial model) { - // Collect all unique 2-D textures used in the model. - Set textureKeys = new HashSet<>(20); - for (Material materials : MySpatial.listMaterials(model, null)) { - for (MatParam matParam : materials.getParams()) { - if (matParam.getVarType() == VarType.Texture2D) { - Texture texture = (Texture) matParam.getValue(); - TextureKey key = (TextureKey) texture.getKey(); - textureKeys.add(key); - } - } - } - - // Write each texture to a JPG file. - for (TextureKey textureKey : textureKeys) { - writeImage(textureKey); - } - } - - /** - * Write the specified model to a J3O file. - * - * @param model the scene-graph subtree to write (not null) - * @param writeAssetPath the asset-path portion of the filename (not null, - * not empty, should end in ".j3o") - */ - private static void writeToJ3O(Spatial model, String writeAssetPath) { - String writeFilePath - = String.format("%s/%s", assetDirPath, writeAssetPath); - Heart.writeJ3O(writeFilePath, model); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.models; + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.AssetInfo; +import com.jme3.asset.BlenderKey; +import com.jme3.asset.TextureKey; +import com.jme3.material.MatParam; +import com.jme3.material.Material; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.UserData; +import com.jme3.scene.plugins.blender.BlenderLoader; +import com.jme3.scene.plugins.blender.meshes.TemporalMesh; +import com.jme3.scene.plugins.ogre.MaterialLoader; +import com.jme3.scene.plugins.ogre.MeshLoader; +import com.jme3.shader.VarType; +import com.jme3.system.JmeContext; +import com.jme3.texture.Texture; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import jme3utilities.Heart; +import jme3utilities.MySkeleton; +import jme3utilities.MySpatial; +import jme3utilities.MyString; + +/** + * A headless SimpleApplication to import certain C-G models used in the + * MinieExamples sub-project. + * + * @author Stephen Gold sgold@sonic.net + */ +public class ImportCgms extends SimpleApplication { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(ImportCgms.class.getName()); + /** + * filesystem path to the destination asset root (the directory/folder for + * output) + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the ImportCgms application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Instantiate the application. + ImportCgms application = new ImportCgms(); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Import the C-G models. + application.start(JmeContext.Type.Headless); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Convert non-J3O format computer-graphics models to J3O format for faster + * loading. Also write out the textures used by those models. + */ + @Override + public void simpleInitApp() { + Logger.getLogger(MaterialLoader.class.getName()).setLevel(Level.SEVERE); + Logger.getLogger(MeshLoader.class.getName()).setLevel(Level.SEVERE); + Logger.getLogger(TemporalMesh.class.getName()).setLevel(Level.SEVERE); + + // Import the BaseMesh model from jme3-testdata-3.1.0-stable.jar: + assetManager.registerLoader(BlenderLoader.class, "blend"); + BlenderKey blendKey = new BlenderKey("Blender/2.4x/BaseMesh_249.blend"); + Spatial baseMesh = assetManager.loadModel(blendKey); + AnimMigrationUtils.migrate(baseMesh); + + // Ensure that every Joint has a name. + List list = MySkeleton.listArmatures(baseMesh, null); + assert list.size() == 1 : list.size(); + Armature armature = list.get(0); + int numJoints = armature.getJointCount(); + for (int jointIndex = 0; jointIndex < numJoints; ++jointIndex) { + Joint joint = armature.getJoint(jointIndex); + if (joint.getName().isEmpty()) { + joint.setName("bone_" + jointIndex); + } + } + writeToJ3O(baseMesh, "Models/BaseMesh/BaseMesh.j3o"); + /* + * Import the Duck model (by Sony Computer Entertainment Inc.) + * from src/main/resources: + */ + Spatial duck = assetManager.loadModel("Models/Duck/Duck.gltf"); + writeToJ3O(duck, "Models/Duck/Duck.j3o"); + writeTextures(duck); + + // Import the Elephant model from jme3-testdata-3.1.0-stable.jar: + Spatial elephant + = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); + writeToJ3O(elephant, "Models/Elephant/Elephant.j3o"); + writeTextures(elephant); + /* + * Import the Jaime model (by Rémy Bouquet) + * from jme3-testdata-3.1.0-stable.jar: + */ + Spatial jaime = assetManager.loadModel("Models/Jaime/Jaime.j3o"); + AnimMigrationUtils.migrate(jaime); + writeToJ3O(jaime, "Models/Jaime/Jaime-new.j3o"); + /* + * Import the MhGame model (by Stephen Gold) + * from src/main/resources: + */ + Spatial mhGame + = assetManager.loadModel("Models/MhGame/MhGame.mesh.xml"); + writeToJ3O(mhGame, "Models/MhGame/MhGame.j3o"); + writeTextures(mhGame); + + // Import the MonkeyHead model from jme3-testdata-3.1.0-stable.jar: + Spatial monkeyHead = assetManager.loadModel( + "Models/MonkeyHead/MonkeyHead.mesh.xml"); + writeToJ3O(monkeyHead, "Models/MonkeyHead/MonkeyHead.j3o"); + writeTextures(monkeyHead); + + // Import the Ninja model from jme3-testdata-3.1.0-stable.jar: + Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); + writeToJ3O(ninja, "Models/Ninja/Ninja.j3o"); + writeTextures(ninja); + /* + * Import the CandyDish model (by Stephen Gold) + * from src/main/resources: + */ + Spatial candyDish + = assetManager.loadModel("Models/CandyDish/CandyDish.glb"); + writeToJ3O(candyDish, "Models/CandyDish/CandyDish.j3o"); + /* + * Import the Oto model (by OtoTheCleaner) + * from jme3-testdata-3.1.0-stable.jar: + */ + Spatial oto = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + writeToJ3O(oto, "Models/Oto/Oto.j3o"); + writeTextures(oto); + /* + * Import the Sinbad model (by Zi Ye) + * from jme3-testdata-3.1.0-stable.jar: + */ + Node sinbad = (Node) assetManager.loadModel( + "Models/Sinbad/Sinbad.mesh.xml"); + sinbad.getChild(4).setUserData(UserData.JME_PHYSICSIGNORE, true); + writeToJ3O(sinbad, "Models/Sinbad/Sinbad.j3o"); + writeTextures(sinbad); + + Spatial sword = assetManager.loadModel("Models/Sinbad/Sword.mesh.xml"); + writeToJ3O(sword, "Models/Sinbad/Sword.j3o"); + writeTextures(sword); + + stop(); + } + // ************************************************************************* + // private methods + + /** + * Write the image of a texture to JPG or PNG file. + * + * @param key access the texture to be written (not null) + */ + private void writeImage(TextureKey key) { + String suffix = key.getExtension(); + String assetPath = key.getName(); + + AssetInfo info = assetManager.locateAsset(key); + InputStream stream = info.openStream(); + + BufferedImage image; + try { + image = ImageIO.read(stream); + } catch (IOException exception) { + logger.log(Level.SEVERE, "failed to read {0}", + MyString.quote(assetPath)); + throw new RuntimeException(exception); + } + + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + File file = new File(writeFilePath); + + // Create the parent folder. + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) { + boolean success = parent.mkdirs(); + if (!success) { + String message = "mkdirs() failed while saving " + + MyString.quote(writeFilePath); + throw new RuntimeException(message); + } + } + /* + * Write the texture's BufferedImage in the + * format specified by its suffix. + */ + try { + ImageIO.write(image, suffix, file); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + logger.log(Level.INFO, "wrote file {0}", MyString.quote(writeFilePath)); + } + + /** + * Write the image of each 2-D texture used in the specified model. + * + * @param model the scene-graph subtree to analyze (may be null) + */ + private void writeTextures(Spatial model) { + // Collect all unique 2-D textures used in the model. + Set textureKeys = new HashSet<>(20); + for (Material materials : MySpatial.listMaterials(model, null)) { + for (MatParam matParam : materials.getParams()) { + if (matParam.getVarType() == VarType.Texture2D) { + Texture texture = (Texture) matParam.getValue(); + TextureKey key = (TextureKey) texture.getKey(); + textureKeys.add(key); + } + } + } + + // Write each texture to a JPG file. + for (TextureKey textureKey : textureKeys) { + writeImage(textureKey); + } + } + + /** + * Write the specified model to a J3O file. + * + * @param model the scene-graph subtree to write (not null) + * @param writeAssetPath the asset-path portion of the filename (not null, + * not empty, should end in ".j3o") + */ + private static void writeToJ3O(Spatial model, String writeAssetPath) { + String writeFilePath + = String.format("%s/%s", assetDirPath, writeAssetPath); + Heart.writeJ3O(writeFilePath, model); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/models/package-info.java b/MinieAssets/src/main/java/jme3utilities/minie/test/models/package-info.java index 6c54892c6..a1e709b54 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/models/package-info.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/models/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2019, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes to generate C-G models for use in the MinieExamples sub-project. - */ -package jme3utilities.minie.test.models; +/* + Copyright (c) 2019, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes to generate C-G models for use in the MinieExamples sub-project. + */ +package jme3utilities.minie.test.models; diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeAnkh.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeAnkh.java index d1fd237c4..4c773c5f4 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeAnkh.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeAnkh.java @@ -1,132 +1,132 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.gltf.GlbLoader; -import com.jme3.system.NativeLibraryLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import vhacd.VHACDParameters; - -/** - * A console application to generate the collision-shape asset "ankh.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeAnkh { - // ************************************************************************* - // constants and loggers - - /** - * which mesh decomposition to use - */ - final private static boolean useManualDecomposition = true; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeAnkh.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeAnkh() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeAnkh application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeAnkh(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for an ankh. - */ - private static void makeAnkh() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(GlbLoader.class, "glb"); - assetManager.registerLoader(J3MLoader.class, "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the Ankh model (by Stephen Gold) - * from src/main/resources: - */ - Spatial cgmRoot = assetManager.loadModel("Models/Ankh/Ankh.glb"); - - // Generate a CollisionShape to approximate the Mesh. - CompoundCollisionShape shape; - if (useManualDecomposition) { - shape = (CompoundCollisionShape) - CollisionShapeFactory.createDynamicMeshShape(cgmRoot); - } else { - VHACDParameters parameters = new VHACDParameters(); - parameters.setVoxelResolution(30_000); - shape = ShapeUtils.createVhacdShape( - cgmRoot, parameters, "MakeAnkh"); - } - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/ankh.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.GlbLoader; +import com.jme3.system.NativeLibraryLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import vhacd.VHACDParameters; + +/** + * A console application to generate the collision-shape asset "ankh.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeAnkh { + // ************************************************************************* + // constants and loggers + + /** + * which mesh decomposition to use + */ + final private static boolean useManualDecomposition = true; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeAnkh.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeAnkh() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeAnkh application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeAnkh(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for an ankh. + */ + private static void makeAnkh() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(GlbLoader.class, "glb"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the Ankh model (by Stephen Gold) + * from src/main/resources: + */ + Spatial cgmRoot = assetManager.loadModel("Models/Ankh/Ankh.glb"); + + // Generate a CollisionShape to approximate the Mesh. + CompoundCollisionShape shape; + if (useManualDecomposition) { + shape = (CompoundCollisionShape) + CollisionShapeFactory.createDynamicMeshShape(cgmRoot); + } else { + VHACDParameters parameters = new VHACDParameters(); + parameters.setVoxelResolution(30_000); + shape = ShapeUtils.createVhacdShape( + cgmRoot, parameters, "MakeAnkh"); + } + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/ankh.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBanana.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBanana.java index ae5331ffa..3c56237df 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBanana.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBanana.java @@ -1,133 +1,133 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.gltf.GlbLoader; -import com.jme3.system.NativeLibraryLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import vhacd.VHACDParameters; - -/** - * A console application to generate the collision-shape asset "banana.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeBanana { - // ************************************************************************* - // constants and loggers - - /** - * which mesh decomposition to use - */ - final private static boolean useManualDecomposition = true; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeBanana.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeBanana() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeBanana application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeBanana(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a banana. - */ - private static void makeBanana() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(GlbLoader.class, "glb"); - assetManager.registerLoader(J3MLoader.class, "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the Banana model (by Stephen Gold) - * from src/main/resources: - */ - Spatial cgmRoot - = assetManager.loadModel("Models/Banana/Banana.glb"); - - // Generate a CollisionShape to approximate the Mesh. - CompoundCollisionShape shape; - if (useManualDecomposition) { - shape = (CompoundCollisionShape) - CollisionShapeFactory.createDynamicMeshShape(cgmRoot); - } else { - VHACDParameters parameters = new VHACDParameters(); - parameters.setVoxelResolution(300_000); - shape = ShapeUtils.createVhacdShape( - cgmRoot, parameters, "MakeBanana"); - } - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/banana.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.GlbLoader; +import com.jme3.system.NativeLibraryLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import vhacd.VHACDParameters; + +/** + * A console application to generate the collision-shape asset "banana.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeBanana { + // ************************************************************************* + // constants and loggers + + /** + * which mesh decomposition to use + */ + final private static boolean useManualDecomposition = true; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeBanana.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeBanana() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeBanana application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeBanana(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a banana. + */ + private static void makeBanana() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(GlbLoader.class, "glb"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the Banana model (by Stephen Gold) + * from src/main/resources: + */ + Spatial cgmRoot + = assetManager.loadModel("Models/Banana/Banana.glb"); + + // Generate a CollisionShape to approximate the Mesh. + CompoundCollisionShape shape; + if (useManualDecomposition) { + shape = (CompoundCollisionShape) + CollisionShapeFactory.createDynamicMeshShape(cgmRoot); + } else { + VHACDParameters parameters = new VHACDParameters(); + parameters.setVoxelResolution(300_000); + shape = ShapeUtils.createVhacdShape( + cgmRoot, parameters, "MakeBanana"); + } + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/banana.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBarrel.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBarrel.java index 6415eb49b..d6589b872 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBarrel.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBarrel.java @@ -1,122 +1,122 @@ -/* - Copyright (c) 2020-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.gltf.GlbLoader; -import com.jme3.system.NativeLibraryLoader; -import java.nio.FloatBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyMesh; -import jme3utilities.MyString; -import jme3utilities.math.VectorSet; - -/** - * A console application to generate the collision-shape asset "barrel.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeBarrel { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeBarrel.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeBarrel() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeBarrel application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeBarrel(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a barrel or cask. - */ - private static void makeBarrel() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(GlbLoader.class, "glb"); - assetManager.registerLoader(J3MLoader.class, "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the Barrel model (by Stephen Gold) - * from src/main/resources: - */ - Spatial cgmRoot = assetManager.loadModel("Models/Barrel/Barrel.glb"); - - // Generate a HullCollisionShape based on mesh vertices. - VectorSet locations = MyMesh.listVertexLocations(cgmRoot, null); - FloatBuffer buffer = locations.toBuffer(); - HullCollisionShape shape = new HullCollisionShape(buffer); - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/barrel.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2020-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.GlbLoader; +import com.jme3.system.NativeLibraryLoader; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyMesh; +import jme3utilities.MyString; +import jme3utilities.math.VectorSet; + +/** + * A console application to generate the collision-shape asset "barrel.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeBarrel { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeBarrel.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeBarrel() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeBarrel application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeBarrel(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a barrel or cask. + */ + private static void makeBarrel() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(GlbLoader.class, "glb"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the Barrel model (by Stephen Gold) + * from src/main/resources: + */ + Spatial cgmRoot = assetManager.loadModel("Models/Barrel/Barrel.glb"); + + // Generate a HullCollisionShape based on mesh vertices. + VectorSet locations = MyMesh.listVertexLocations(cgmRoot, null); + FloatBuffer buffer = locations.toBuffer(); + HullCollisionShape shape = new HullCollisionShape(buffer); + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/barrel.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBowlingPin.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBowlingPin.java index db77cb106..6768238b5 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBowlingPin.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeBowlingPin.java @@ -1,133 +1,133 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.gltf.GlbLoader; -import com.jme3.system.NativeLibraryLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import vhacd.VHACDParameters; - -/** - * A console application to generate the collision-shape asset "bowlingPin.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeBowlingPin { - // ************************************************************************* - // constants and loggers - - /** - * which mesh decomposition to use - */ - final private static boolean useManualDecomposition = true; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeBowlingPin.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeBowlingPin() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeBowlingPin application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeBowlingPin(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a bowling pin. - */ - private static void makeBowlingPin() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(GlbLoader.class, "glb"); - assetManager.registerLoader(J3MLoader.class, "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the BowlingPin model (by Stephen Gold) - * from src/main/resources: - */ - Spatial cgmRoot - = assetManager.loadModel("Models/BowlingPin/BowlingPin.glb"); - - // Generate a CollisionShape to approximate the Mesh. - CompoundCollisionShape shape; - if (useManualDecomposition) { - shape = (CompoundCollisionShape) - CollisionShapeFactory.createDynamicMeshShape(cgmRoot); - } else { - VHACDParameters parameters = new VHACDParameters(); - parameters.setVoxelResolution(1_300_000); - shape = ShapeUtils.createVhacdShape( - cgmRoot, parameters, "MakeBowlingPin"); - } - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/bowlingPin.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.GlbLoader; +import com.jme3.system.NativeLibraryLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import vhacd.VHACDParameters; + +/** + * A console application to generate the collision-shape asset "bowlingPin.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeBowlingPin { + // ************************************************************************* + // constants and loggers + + /** + * which mesh decomposition to use + */ + final private static boolean useManualDecomposition = true; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeBowlingPin.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeBowlingPin() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeBowlingPin application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeBowlingPin(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a bowling pin. + */ + private static void makeBowlingPin() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(GlbLoader.class, "glb"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the BowlingPin model (by Stephen Gold) + * from src/main/resources: + */ + Spatial cgmRoot + = assetManager.loadModel("Models/BowlingPin/BowlingPin.glb"); + + // Generate a CollisionShape to approximate the Mesh. + CompoundCollisionShape shape; + if (useManualDecomposition) { + shape = (CompoundCollisionShape) + CollisionShapeFactory.createDynamicMeshShape(cgmRoot); + } else { + VHACDParameters parameters = new VHACDParameters(); + parameters.setVoxelResolution(1_300_000); + shape = ShapeUtils.createVhacdShape( + cgmRoot, parameters, "MakeBowlingPin"); + } + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/bowlingPin.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeDuck.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeDuck.java index ff40a5535..cddbb5563 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeDuck.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeDuck.java @@ -1,145 +1,145 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.gltf.BinLoader; -import com.jme3.scene.plugins.gltf.GltfLoader; -import com.jme3.system.NativeLibraryLoader; -import com.jme3.texture.plugins.AWTLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import vhacd.VHACDParameters; - -/** - * A console application to generate the collision-shape asset "duck.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeDuck { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeDuck.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeDuck() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeDuck application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeDuck(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a toy duck. - */ - private static void makeDuck() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(AWTLoader.class, "png"); - assetManager.registerLoader(BinLoader.class, "bin"); - assetManager.registerLoader(GltfLoader.class, "gltf"); - assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the Duck model (by Sony Computer Entertainment Inc.) - * from src/main/resources: - */ - Spatial cgmRoot = assetManager.loadModel("Models/Duck/Duck.gltf"); - Spatial parent = ((Node) cgmRoot).getChild(0); - parent.setLocalTransform(Transform.IDENTITY); - Spatial geom = ((Node) parent).getChild(0); - - // Translate and uniformly scale the model to fit inside a 2x2x2 cube. - Vector3f[] minMax = MySpatial.findMinMaxCoords(geom); - Vector3f center = MyVector3f.midpoint(minMax[0], minMax[1], null); - Vector3f offset = center.negate(); - geom.setLocalTranslation(offset); - - Vector3f extents = minMax[1].subtract(minMax[0]); - float radius = MyMath.max(extents.x, extents.y, extents.z) / 2f; - parent.setLocalScale(1f / radius); - - // Generate a CollisionShape to approximate the Mesh. - VHACDParameters parameters = new VHACDParameters(); - parameters.setMaxVerticesPerHull(99); - parameters.setVoxelResolution(900_000); - CompoundCollisionShape shape - = ShapeUtils.createVhacdShape(cgmRoot, parameters, "MakeDuck"); - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/duck.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.BinLoader; +import com.jme3.scene.plugins.gltf.GltfLoader; +import com.jme3.system.NativeLibraryLoader; +import com.jme3.texture.plugins.AWTLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import vhacd.VHACDParameters; + +/** + * A console application to generate the collision-shape asset "duck.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeDuck { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeDuck.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeDuck() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeDuck application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeDuck(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a toy duck. + */ + private static void makeDuck() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(AWTLoader.class, "png"); + assetManager.registerLoader(BinLoader.class, "bin"); + assetManager.registerLoader(GltfLoader.class, "gltf"); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the Duck model (by Sony Computer Entertainment Inc.) + * from src/main/resources: + */ + Spatial cgmRoot = assetManager.loadModel("Models/Duck/Duck.gltf"); + Spatial parent = ((Node) cgmRoot).getChild(0); + parent.setLocalTransform(Transform.IDENTITY); + Spatial geom = ((Node) parent).getChild(0); + + // Translate and uniformly scale the model to fit inside a 2x2x2 cube. + Vector3f[] minMax = MySpatial.findMinMaxCoords(geom); + Vector3f center = MyVector3f.midpoint(minMax[0], minMax[1], null); + Vector3f offset = center.negate(); + geom.setLocalTranslation(offset); + + Vector3f extents = minMax[1].subtract(minMax[0]); + float radius = MyMath.max(extents.x, extents.y, extents.z) / 2f; + parent.setLocalScale(1f / radius); + + // Generate a CollisionShape to approximate the Mesh. + VHACDParameters parameters = new VHACDParameters(); + parameters.setMaxVerticesPerHull(99); + parameters.setVoxelResolution(900_000); + CompoundCollisionShape shape + = ShapeUtils.createVhacdShape(cgmRoot, parameters, "MakeDuck"); + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/duck.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeGlyphs.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeGlyphs.java index 7f6de3e82..fc651274b 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeGlyphs.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeGlyphs.java @@ -1,244 +1,244 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.atr.jme.font.TrueTypeFont; -import com.atr.jme.font.asset.TrueTypeKeyMesh; -import com.atr.jme.font.asset.TrueTypeLoader; -import com.atr.jme.font.util.StringContainer; -import com.atr.jme.font.util.Style; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.system.NativeLibraryLoader; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyMesh; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; - -/** - * A console application to generate collision-shape assets for all 26 letters - * of the English alphabet in upper case. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeGlyphs { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeGlyphs.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // fields - - /** - * list of vertices in a triangular prism - */ - final private static List prismVertices - = new ArrayList<>(2 * MyMesh.vpt); - /** - * individual vertices in the above list - */ - final private static Vector3f v0a = new Vector3f(); - final private static Vector3f v0b = new Vector3f(); - final private static Vector3f v1a = new Vector3f(); - final private static Vector3f v1b = new Vector3f(); - final private static Vector3f v2a = new Vector3f(); - final private static Vector3f v2b = new Vector3f(); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeGlyphs() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeGlyphs application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shapes. - makeAlphabet(); - } - // ************************************************************************* - // private methods - - /** - * Load ProFont from a classpath asset, using JME-TTF. - * - * @return a new font instance - */ - @SuppressWarnings("unchecked") - private static TrueTypeFont loadProFont() { - DesktopAssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLocator(null, ClasspathLocator.class); - assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); - assetManager.registerLoader(TrueTypeLoader.class, "ttf"); - - String fontAssetPath = "Interface/Fonts/ProFontWindows.ttf"; - Style style = Style.Plain; - int pointSize = 9; - TrueTypeKeyMesh assetKey - = new TrueTypeKeyMesh(fontAssetPath, style, pointSize); - TrueTypeFont result = assetManager.loadAsset(assetKey); - - return result; - } - - /** - * Generate collision shapes for all 26 letters of the English alphabet in - * upper case. - */ - private static void makeAlphabet() { - // Initialize the vertex list. - prismVertices.clear(); - prismVertices.add(v0a); - prismVertices.add(v0b); - prismVertices.add(v1a); - prismVertices.add(v1b); - prismVertices.add(v2a); - prismVertices.add(v2b); - - // Generate the shapes. - TrueTypeFont font = loadProFont(); - Transform transformA = new Transform(); - transformA.getTranslation().set(0f, 0f, 0.5f); - Transform transformB = new Transform(); - transformB.getTranslation().set(0f, 0f, -0.5f); - for (char letter = 'A'; letter <= 'Z'; ++letter) { - makeGlyphShape(font, letter, transformA, transformB); - } - for (char letter = '0'; letter <= '9'; ++letter) { - makeGlyphShape(font, letter, transformA, transformB); - } - } - - /** - * Generate a collision shape for the specified character. - * - * @param font (not null) - * @param character which character - * @param transformA the first coordinate transform to apply to triangles - * (not null, unaffected) - * @param transformB the 2nd coordinate transform to apply to triangles (not - * null, unaffected) - */ - private static void makeGlyphShape(TrueTypeFont font, char character, - Transform transformA, Transform transformB) { - // Convert the specified character to a TrueTypeNode. - String string = Character.toString(character); - int kerning = 0; - ColorRGBA color = null; - StringContainer.Align hAlign = StringContainer.Align.Center; - StringContainer.VAlign vAlign = StringContainer.VAlign.Center; - Spatial ttNode = font.getText(string, kerning, color, hAlign, vAlign); - - // Access the generated mesh. - List list = MySpatial.listGeometries(ttNode); - assert list.size() == 1; - Mesh mesh = list.get(0).getMesh(); - assert mesh.getMode() == Mesh.Mode.Triangles : mesh.getMode(); - - CompoundCollisionShape compoundShape = new CompoundCollisionShape(); - /* - * For each triangle in the mesh, add a triangular prism - * to the compound shape. - */ - IndexBuffer triangleIndices = mesh.getIndicesAsList(); - int numIndices = triangleIndices.size(); - FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); - for (int startOff = 0; startOff < numIndices; startOff += MyMesh.vpt) { - int ti0 = triangleIndices.get(startOff); - int ti1 = triangleIndices.get(startOff + 1); - int ti2 = triangleIndices.get(startOff + 2); - - MyBuffer.get(positions, MyVector3f.numAxes * ti0, v0a); - MyBuffer.get(positions, MyVector3f.numAxes * ti1, v1a); - MyBuffer.get(positions, MyVector3f.numAxes * ti2, v2a); - - MyMath.transform(transformB, v0a, v0b); - MyMath.transform(transformB, v1a, v1b); - MyMath.transform(transformB, v2a, v2b); - - MyMath.transform(transformA, v0a, v0a); - MyMath.transform(transformA, v1a, v1a); - MyMath.transform(transformA, v2a, v2a); - - CollisionShape prism = new HullCollisionShape(prismVertices); - compoundShape.addChildShape(prism); - } - - // Write the compound shape to a J3O file. - String assetPath - = String.format("CollisionShapes/glyphs/%s.j3o", string); - String filePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(filePath, compoundShape); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.atr.jme.font.TrueTypeFont; +import com.atr.jme.font.asset.TrueTypeKeyMesh; +import com.atr.jme.font.asset.TrueTypeLoader; +import com.atr.jme.font.util.StringContainer; +import com.atr.jme.font.util.Style; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.system.NativeLibraryLoader; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyMesh; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; + +/** + * A console application to generate collision-shape assets for all 26 letters + * of the English alphabet in upper case. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeGlyphs { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeGlyphs.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // fields + + /** + * list of vertices in a triangular prism + */ + final private static List prismVertices + = new ArrayList<>(2 * MyMesh.vpt); + /** + * individual vertices in the above list + */ + final private static Vector3f v0a = new Vector3f(); + final private static Vector3f v0b = new Vector3f(); + final private static Vector3f v1a = new Vector3f(); + final private static Vector3f v1b = new Vector3f(); + final private static Vector3f v2a = new Vector3f(); + final private static Vector3f v2b = new Vector3f(); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeGlyphs() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeGlyphs application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shapes. + makeAlphabet(); + } + // ************************************************************************* + // private methods + + /** + * Load ProFont from a classpath asset, using JME-TTF. + * + * @return a new font instance + */ + @SuppressWarnings("unchecked") + private static TrueTypeFont loadProFont() { + DesktopAssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLocator(null, ClasspathLocator.class); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + assetManager.registerLoader(TrueTypeLoader.class, "ttf"); + + String fontAssetPath = "Interface/Fonts/ProFontWindows.ttf"; + Style style = Style.Plain; + int pointSize = 9; + TrueTypeKeyMesh assetKey + = new TrueTypeKeyMesh(fontAssetPath, style, pointSize); + TrueTypeFont result = assetManager.loadAsset(assetKey); + + return result; + } + + /** + * Generate collision shapes for all 26 letters of the English alphabet in + * upper case. + */ + private static void makeAlphabet() { + // Initialize the vertex list. + prismVertices.clear(); + prismVertices.add(v0a); + prismVertices.add(v0b); + prismVertices.add(v1a); + prismVertices.add(v1b); + prismVertices.add(v2a); + prismVertices.add(v2b); + + // Generate the shapes. + TrueTypeFont font = loadProFont(); + Transform transformA = new Transform(); + transformA.getTranslation().set(0f, 0f, 0.5f); + Transform transformB = new Transform(); + transformB.getTranslation().set(0f, 0f, -0.5f); + for (char letter = 'A'; letter <= 'Z'; ++letter) { + makeGlyphShape(font, letter, transformA, transformB); + } + for (char letter = '0'; letter <= '9'; ++letter) { + makeGlyphShape(font, letter, transformA, transformB); + } + } + + /** + * Generate a collision shape for the specified character. + * + * @param font (not null) + * @param character which character + * @param transformA the first coordinate transform to apply to triangles + * (not null, unaffected) + * @param transformB the 2nd coordinate transform to apply to triangles (not + * null, unaffected) + */ + private static void makeGlyphShape(TrueTypeFont font, char character, + Transform transformA, Transform transformB) { + // Convert the specified character to a TrueTypeNode. + String string = Character.toString(character); + int kerning = 0; + ColorRGBA color = null; + StringContainer.Align hAlign = StringContainer.Align.Center; + StringContainer.VAlign vAlign = StringContainer.VAlign.Center; + Spatial ttNode = font.getText(string, kerning, color, hAlign, vAlign); + + // Access the generated mesh. + List list = MySpatial.listGeometries(ttNode); + assert list.size() == 1; + Mesh mesh = list.get(0).getMesh(); + assert mesh.getMode() == Mesh.Mode.Triangles : mesh.getMode(); + + CompoundCollisionShape compoundShape = new CompoundCollisionShape(); + /* + * For each triangle in the mesh, add a triangular prism + * to the compound shape. + */ + IndexBuffer triangleIndices = mesh.getIndicesAsList(); + int numIndices = triangleIndices.size(); + FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); + for (int startOff = 0; startOff < numIndices; startOff += MyMesh.vpt) { + int ti0 = triangleIndices.get(startOff); + int ti1 = triangleIndices.get(startOff + 1); + int ti2 = triangleIndices.get(startOff + 2); + + MyBuffer.get(positions, MyVector3f.numAxes * ti0, v0a); + MyBuffer.get(positions, MyVector3f.numAxes * ti1, v1a); + MyBuffer.get(positions, MyVector3f.numAxes * ti2, v2a); + + MyMath.transform(transformB, v0a, v0b); + MyMath.transform(transformB, v1a, v1b); + MyMath.transform(transformB, v2a, v2b); + + MyMath.transform(transformA, v0a, v0a); + MyMath.transform(transformA, v1a, v1a); + MyMath.transform(transformA, v2a, v2a); + + CollisionShape prism = new HullCollisionShape(prismVertices); + compoundShape.addChildShape(prism); + } + + // Write the compound shape to a J3O file. + String assetPath + = String.format("CollisionShapes/glyphs/%s.j3o", string); + String filePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(filePath, compoundShape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHeart.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHeart.java index 85df31915..c8bde19bb 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHeart.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHeart.java @@ -1,345 +1,345 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import com.jme3.system.NativeLibraryLoader; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.noise.Generator; - -/** - * A console application to generate the collision-shape asset "heart.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeHeart { - // ************************************************************************* - // constants and loggers - - /** - * maximum uncertainty for solutions (>0) - */ - final private static double tolerance = 1e-7; - /** - * number of axes in a vector - */ - final private static int numAxes = 3; - /** - * number of sample points used to generate the convex hull - */ - final private static int numSamples = 200; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeHeart.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // fields - - /** - * coordinate locations of sample points - */ - final private static FloatBuffer sampleBuffer - = BufferUtils.createFloatBuffer(numAxes * numSamples); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeHeart() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeHeart application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeHeart(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a 3-D heart. - */ - private static void makeHeart() { - // Phase 1: add solutions for y=0, x>0 - int n1 = 30; - for (int i = 0; i < n1; ++i) { - double gamma = i * Math.PI / (n1 - 1); - solve1(gamma); - } - - // Phase 2: add solutions for x=0, y>0 (symmetric in y) - int n2 = 15; - for (int i = 0; i < n2; ++i) { - double beta = (i + 1) * Math.PI / n2; - solve2(beta); - } - - // Phase 3: add solutions for x>0, y>0 (symmetric in y) - Generator generate = new Generator(); - while (sampleBuffer.remaining() >= 2 * numAxes) { - double x = 1.2 * generate.nextDouble(); - double z = -0.9 + 2.2 * generate.nextDouble(); - solve3(x, z); - } - /* - * Phase 4: combine 2 hulls (one with a 180-degree rotation) - * to generate a compound shape. - */ - Vector3f scale = new Vector3f(1f, 0.7f, 1f); - MyBuffer.scale(sampleBuffer, 0, sampleBuffer.limit(), scale); - HullCollisionShape half = new HullCollisionShape(sampleBuffer); - CompoundCollisionShape compound = new CompoundCollisionShape(); - compound.addChildShape(half); - Matrix3f rotation = new Matrix3f(); - rotation.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z); - compound.addChildShape(half, Vector3f.ZERO, rotation); - - // Phase 5: write the shape to the asset file. - String assetPath = "CollisionShapes/heart.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, compound); - } - - /** - * Evaluate the parametric function for the shape. The formula was adapted - * from https://commons.wikimedia.org/wiki/File:Heart3D.png - * - * @param x the X coordinate of the point to test - * @param y the Y coordinate of the point to test - * @param z the Z coordinate of the point to test - * @return error - */ - private static double plug(double x, double y, double z) { - double x2 = x * x; - double y2 = y * y; - double z2 = z * z; - double subExp = x2 + 2.25 * y2 + z2 - 1.0; - double r3 = subExp * subExp * subExp; - double z3 = z2 * z; - double error = r3 - (x2 + 0.045 * y2) * z3; - - return error; - } - - /** - * Use bisection search to find a value of R for which (R sin gamma, 0, R - * cos gamma) is a zero of the parametric function. Always adds one sample - * to the buffer. - * - * @param gamma angle from the +Z axis (in radians) - */ - private static void solve1(double gamma) { - double sinGamma = Math.sin(gamma); - if (sinGamma < 0.0 && sinGamma > -1e-10) { - sinGamma = 0.0; - } - double cosGamma = Math.cos(gamma); - - double r1 = 0.75; - boolean s1 = trial1(cosGamma, sinGamma, r1); - double r2 = 1.5; - boolean s2 = trial1(cosGamma, sinGamma, r2); - assert s1 != s2; - if (s1) { - double r = r1; - r1 = r2; - r2 = r; - } - - while (Math.abs(r1 - r2) > tolerance) { - assert !trial1(cosGamma, sinGamma, r1); - assert trial1(cosGamma, sinGamma, r2); - - // Bisect to obtain a new estimate of the solution. - double r = (r1 + r2) / 2.0; - boolean s = trial1(cosGamma, sinGamma, r); - if (s) { - r2 = r; - } else { - r1 = r; - } - } - double r = (r1 + r2) / 2.0; - double x = r * sinGamma; - double z = r * cosGamma; - writeSample(x, 0.0, z); - } - - /** - * Use bisection search to find a value of R for which (0, R sin gamma, R - * cos gamma) is a zero of the parametric function. Always adds 2 samples to - * the buffer. - * - * @param gamma angle from the +Z axis (in radians) - */ - private static void solve2(double gamma) { - double sinGamma = Math.sin(gamma); - double cosGamma = Math.cos(gamma); - - double r1 = 0.65; - boolean s1 = trial2(cosGamma, sinGamma, r1); - double r2 = 1.1; - boolean s2 = trial2(cosGamma, sinGamma, r2); - assert s1 != s2; - if (s1) { - double r = r1; - r1 = r2; - r2 = r; - } - - while (Math.abs(r1 - r2) > tolerance) { - assert !trial2(cosGamma, sinGamma, r1); - assert trial2(cosGamma, sinGamma, r2); - - // Bisect to obtain a new estimate of R. - double r = (r1 + r2) / 2.0; - boolean s = trial2(cosGamma, sinGamma, r); - if (s) { - r2 = r; - } else { - r1 = r; - } - } - - double r = (r1 + r2) / 2.0; - double y = r * sinGamma; - double z = r * cosGamma; - writeSample(0.0, y, z); - writeSample(0.0, -y, z); - } - - /** - * Use bisection search to find a value of R for which (x, R, z) is a zero - * of the parametric function. If successful, adds 2 samples to the buffer. - * - * @param x the X coordinate to solve for - * @param z the Z coordinate to solve for - * @return true if successful, otherwise false - */ - private static boolean solve3(double x, double z) { - double r1 = 0.0; - boolean s1 = trial3(x, z, r1); - double r2 = 0.7; - boolean s2 = trial3(x, z, r2); - if (s1 == s2) { - return false; - } - if (s1) { - double r = r1; - r1 = r2; - r2 = r; - } - - while (Math.abs(r1 - r2) > tolerance) { - assert !trial3(x, z, r1); - assert trial3(x, z, r2); - - // Bisect to obtain a new estimate of the solution. - double r = (r1 + r2) / 2.0; - boolean s = trial3(x, z, r); - if (s) { - r2 = r; - } else { - r1 = r; - } - } - - double r = (r1 + r2) / 2.0; - writeSample(x, r, z); - writeSample(x, -r, z); - return true; - } - - private static boolean trial1(double cosGamma, double sinGamma, double r) { - double x = r * sinGamma; - double z = r * cosGamma; - double error = plug(x, 0.0, z); - - return error > 0.0; - } - - private static boolean trial2(double cosGamma, double sinGamma, double r) { - double y = r * sinGamma; - double z = r * cosGamma; - double error = plug(0.0, y, z); - - return error > 0.0; - } - - private static boolean trial3(double x, double z, double r) { - double error = plug(x, r, z); - return error > 0.0; - } - - /** - * Write a sample point to the buffer. - * - * @param xx X coordinate of the sample (≥0) - * @param yy Y coordinate of the sample - * @param zz Z coordinate of the sample - */ - private static void writeSample(double xx, double yy, double zz) { - float x = (float) xx; - float y = (float) yy; - float z = (float) zz; - sampleBuffer.put(x).put(y).put(z); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.system.NativeLibraryLoader; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.noise.Generator; + +/** + * A console application to generate the collision-shape asset "heart.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeHeart { + // ************************************************************************* + // constants and loggers + + /** + * maximum uncertainty for solutions (>0) + */ + final private static double tolerance = 1e-7; + /** + * number of axes in a vector + */ + final private static int numAxes = 3; + /** + * number of sample points used to generate the convex hull + */ + final private static int numSamples = 200; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeHeart.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // fields + + /** + * coordinate locations of sample points + */ + final private static FloatBuffer sampleBuffer + = BufferUtils.createFloatBuffer(numAxes * numSamples); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeHeart() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeHeart application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeHeart(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a 3-D heart. + */ + private static void makeHeart() { + // Phase 1: add solutions for y=0, x>0 + int n1 = 30; + for (int i = 0; i < n1; ++i) { + double gamma = i * Math.PI / (n1 - 1); + solve1(gamma); + } + + // Phase 2: add solutions for x=0, y>0 (symmetric in y) + int n2 = 15; + for (int i = 0; i < n2; ++i) { + double beta = (i + 1) * Math.PI / n2; + solve2(beta); + } + + // Phase 3: add solutions for x>0, y>0 (symmetric in y) + Generator generate = new Generator(); + while (sampleBuffer.remaining() >= 2 * numAxes) { + double x = 1.2 * generate.nextDouble(); + double z = -0.9 + 2.2 * generate.nextDouble(); + solve3(x, z); + } + /* + * Phase 4: combine 2 hulls (one with a 180-degree rotation) + * to generate a compound shape. + */ + Vector3f scale = new Vector3f(1f, 0.7f, 1f); + MyBuffer.scale(sampleBuffer, 0, sampleBuffer.limit(), scale); + HullCollisionShape half = new HullCollisionShape(sampleBuffer); + CompoundCollisionShape compound = new CompoundCollisionShape(); + compound.addChildShape(half); + Matrix3f rotation = new Matrix3f(); + rotation.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z); + compound.addChildShape(half, Vector3f.ZERO, rotation); + + // Phase 5: write the shape to the asset file. + String assetPath = "CollisionShapes/heart.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, compound); + } + + /** + * Evaluate the parametric function for the shape. The formula was adapted + * from https://commons.wikimedia.org/wiki/File:Heart3D.png + * + * @param x the X coordinate of the point to test + * @param y the Y coordinate of the point to test + * @param z the Z coordinate of the point to test + * @return error + */ + private static double plug(double x, double y, double z) { + double x2 = x * x; + double y2 = y * y; + double z2 = z * z; + double subExp = x2 + 2.25 * y2 + z2 - 1.0; + double r3 = subExp * subExp * subExp; + double z3 = z2 * z; + double error = r3 - (x2 + 0.045 * y2) * z3; + + return error; + } + + /** + * Use bisection search to find a value of R for which (R sin gamma, 0, R + * cos gamma) is a zero of the parametric function. Always adds one sample + * to the buffer. + * + * @param gamma angle from the +Z axis (in radians) + */ + private static void solve1(double gamma) { + double sinGamma = Math.sin(gamma); + if (sinGamma < 0.0 && sinGamma > -1e-10) { + sinGamma = 0.0; + } + double cosGamma = Math.cos(gamma); + + double r1 = 0.75; + boolean s1 = trial1(cosGamma, sinGamma, r1); + double r2 = 1.5; + boolean s2 = trial1(cosGamma, sinGamma, r2); + assert s1 != s2; + if (s1) { + double r = r1; + r1 = r2; + r2 = r; + } + + while (Math.abs(r1 - r2) > tolerance) { + assert !trial1(cosGamma, sinGamma, r1); + assert trial1(cosGamma, sinGamma, r2); + + // Bisect to obtain a new estimate of the solution. + double r = (r1 + r2) / 2.0; + boolean s = trial1(cosGamma, sinGamma, r); + if (s) { + r2 = r; + } else { + r1 = r; + } + } + double r = (r1 + r2) / 2.0; + double x = r * sinGamma; + double z = r * cosGamma; + writeSample(x, 0.0, z); + } + + /** + * Use bisection search to find a value of R for which (0, R sin gamma, R + * cos gamma) is a zero of the parametric function. Always adds 2 samples to + * the buffer. + * + * @param gamma angle from the +Z axis (in radians) + */ + private static void solve2(double gamma) { + double sinGamma = Math.sin(gamma); + double cosGamma = Math.cos(gamma); + + double r1 = 0.65; + boolean s1 = trial2(cosGamma, sinGamma, r1); + double r2 = 1.1; + boolean s2 = trial2(cosGamma, sinGamma, r2); + assert s1 != s2; + if (s1) { + double r = r1; + r1 = r2; + r2 = r; + } + + while (Math.abs(r1 - r2) > tolerance) { + assert !trial2(cosGamma, sinGamma, r1); + assert trial2(cosGamma, sinGamma, r2); + + // Bisect to obtain a new estimate of R. + double r = (r1 + r2) / 2.0; + boolean s = trial2(cosGamma, sinGamma, r); + if (s) { + r2 = r; + } else { + r1 = r; + } + } + + double r = (r1 + r2) / 2.0; + double y = r * sinGamma; + double z = r * cosGamma; + writeSample(0.0, y, z); + writeSample(0.0, -y, z); + } + + /** + * Use bisection search to find a value of R for which (x, R, z) is a zero + * of the parametric function. If successful, adds 2 samples to the buffer. + * + * @param x the X coordinate to solve for + * @param z the Z coordinate to solve for + * @return true if successful, otherwise false + */ + private static boolean solve3(double x, double z) { + double r1 = 0.0; + boolean s1 = trial3(x, z, r1); + double r2 = 0.7; + boolean s2 = trial3(x, z, r2); + if (s1 == s2) { + return false; + } + if (s1) { + double r = r1; + r1 = r2; + r2 = r; + } + + while (Math.abs(r1 - r2) > tolerance) { + assert !trial3(x, z, r1); + assert trial3(x, z, r2); + + // Bisect to obtain a new estimate of the solution. + double r = (r1 + r2) / 2.0; + boolean s = trial3(x, z, r); + if (s) { + r2 = r; + } else { + r1 = r; + } + } + + double r = (r1 + r2) / 2.0; + writeSample(x, r, z); + writeSample(x, -r, z); + return true; + } + + private static boolean trial1(double cosGamma, double sinGamma, double r) { + double x = r * sinGamma; + double z = r * cosGamma; + double error = plug(x, 0.0, z); + + return error > 0.0; + } + + private static boolean trial2(double cosGamma, double sinGamma, double r) { + double y = r * sinGamma; + double z = r * cosGamma; + double error = plug(0.0, y, z); + + return error > 0.0; + } + + private static boolean trial3(double x, double z, double r) { + double error = plug(x, r, z); + return error > 0.0; + } + + /** + * Write a sample point to the buffer. + * + * @param xx X coordinate of the sample (≥0) + * @param yy Y coordinate of the sample + * @param zz Z coordinate of the sample + */ + private static void writeSample(double xx, double yy, double zz) { + float x = (float) xx; + float y = (float) yy; + float z = (float) zz; + sampleBuffer.put(x).put(y).put(z); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHorseshoe.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHorseshoe.java index 773958000..c39e28720 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHorseshoe.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeHorseshoe.java @@ -1,133 +1,133 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.gltf.GlbLoader; -import com.jme3.system.NativeLibraryLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import vhacd.VHACDParameters; - -/** - * A console application to generate the collision-shape asset "horseshoe.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeHorseshoe { - // ************************************************************************* - // constants and loggers - - /** - * which mesh decomposition to use - */ - final private static boolean useManualDecomposition = true; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeHorseshoe.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeHorseshoe() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeHorseshoe application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeHorseshoe(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a horseshoe. - */ - private static void makeHorseshoe() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(GlbLoader.class, "glb"); - assetManager.registerLoader(J3MLoader.class, "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the Horseshoe model (by Stephen Gold) - * from src/main/resources: - */ - Spatial cgmRoot - = assetManager.loadModel("Models/Horseshoe/Horseshoe.glb"); - - // Generate a CollisionShape to approximate the Mesh. - CompoundCollisionShape shape; - if (useManualDecomposition) { - shape = (CompoundCollisionShape) - CollisionShapeFactory.createDynamicMeshShape(cgmRoot); - } else { - VHACDParameters parameters = new VHACDParameters(); - parameters.setVoxelResolution(600_000); - shape = ShapeUtils.createVhacdShape( - cgmRoot, parameters, "MakeHorseshoe"); - } - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/horseshoe.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.GlbLoader; +import com.jme3.system.NativeLibraryLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import vhacd.VHACDParameters; + +/** + * A console application to generate the collision-shape asset "horseshoe.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeHorseshoe { + // ************************************************************************* + // constants and loggers + + /** + * which mesh decomposition to use + */ + final private static boolean useManualDecomposition = true; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeHorseshoe.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeHorseshoe() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeHorseshoe application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeHorseshoe(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a horseshoe. + */ + private static void makeHorseshoe() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(GlbLoader.class, "glb"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the Horseshoe model (by Stephen Gold) + * from src/main/resources: + */ + Spatial cgmRoot + = assetManager.loadModel("Models/Horseshoe/Horseshoe.glb"); + + // Generate a CollisionShape to approximate the Mesh. + CompoundCollisionShape shape; + if (useManualDecomposition) { + shape = (CompoundCollisionShape) + CollisionShapeFactory.createDynamicMeshShape(cgmRoot); + } else { + VHACDParameters parameters = new VHACDParameters(); + parameters.setVoxelResolution(600_000); + shape = ShapeUtils.createVhacdShape( + cgmRoot, parameters, "MakeHorseshoe"); + } + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/horseshoe.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeSword.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeSword.java index a0e67164a..073053d63 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeSword.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeSword.java @@ -1,145 +1,145 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.ogre.MaterialLoader; -import com.jme3.scene.plugins.ogre.MeshLoader; -import com.jme3.system.NativeLibraryLoader; -import com.jme3.texture.plugins.AWTLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import vhacd4.Vhacd4Parameters; - -/** - * A console application to generate the collision-shape asset "sword.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeSword { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeSword.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeSword() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeSword application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeSword(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for Sinbad's scimitar. - */ - private static void makeSword() { - AssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(AWTLoader.class, "jpg"); - assetManager.registerLoader(J3MLoader.class, "j3md"); - assetManager.registerLoader(MaterialLoader.class, "material"); - assetManager.registerLoader(MeshLoader.class, "mesh.xml"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import Sinbad's scimitar model (by Zi Ye) - * from jme3-testdata-3.1.0-stable.jar: - */ - Spatial parent - = assetManager.loadModel("Models/Sinbad/Sword.mesh.xml"); - Node cgmRoot = new Node(); - cgmRoot.attachChild(parent); - - // Translate and uniformly scale the model to fit inside a 2x2x2 cube. - Vector3f[] minMax = MySpatial.findMinMaxCoords(parent); - Vector3f center = MyVector3f.midpoint(minMax[0], minMax[1], null); - Vector3f offset = center.negate(); - for (Spatial geom : ((Node) parent).getChildren()) { - geom.setLocalTranslation(offset); - } - - Vector3f extents = minMax[1].subtract(minMax[0]); - float radius = MyMath.max(extents.x, extents.y, extents.z) / 2f; - parent.setLocalScale(1f / radius); - - // Generate a CollisionShape to approximate the Mesh. - Vhacd4Parameters parameters = new Vhacd4Parameters(); - parameters.setMaxHulls(8); - CompoundCollisionShape shape - = ShapeUtils.createVhacdShape(cgmRoot, parameters, "MakeSword"); - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/sword.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.ogre.MaterialLoader; +import com.jme3.scene.plugins.ogre.MeshLoader; +import com.jme3.system.NativeLibraryLoader; +import com.jme3.texture.plugins.AWTLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import vhacd4.Vhacd4Parameters; + +/** + * A console application to generate the collision-shape asset "sword.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeSword { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeSword.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeSword() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeSword application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeSword(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for Sinbad's scimitar. + */ + private static void makeSword() { + AssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(AWTLoader.class, "jpg"); + assetManager.registerLoader(J3MLoader.class, "j3md"); + assetManager.registerLoader(MaterialLoader.class, "material"); + assetManager.registerLoader(MeshLoader.class, "mesh.xml"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import Sinbad's scimitar model (by Zi Ye) + * from jme3-testdata-3.1.0-stable.jar: + */ + Spatial parent + = assetManager.loadModel("Models/Sinbad/Sword.mesh.xml"); + Node cgmRoot = new Node(); + cgmRoot.attachChild(parent); + + // Translate and uniformly scale the model to fit inside a 2x2x2 cube. + Vector3f[] minMax = MySpatial.findMinMaxCoords(parent); + Vector3f center = MyVector3f.midpoint(minMax[0], minMax[1], null); + Vector3f offset = center.negate(); + for (Spatial geom : ((Node) parent).getChildren()) { + geom.setLocalTranslation(offset); + } + + Vector3f extents = minMax[1].subtract(minMax[0]); + float radius = MyMath.max(extents.x, extents.y, extents.z) / 2f; + parent.setLocalScale(1f / radius); + + // Generate a CollisionShape to approximate the Mesh. + Vhacd4Parameters parameters = new Vhacd4Parameters(); + parameters.setMaxHulls(8); + CompoundCollisionShape shape + = ShapeUtils.createVhacdShape(cgmRoot, parameters, "MakeSword"); + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/sword.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeTeapot.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeTeapot.java index b0ccec6bc..b219a5810 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeTeapot.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/MakeTeapot.java @@ -1,154 +1,154 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.asset.DesktopAssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.export.Savable; -import com.jme3.material.plugins.J3MLoader; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.MTLLoader; -import com.jme3.scene.plugins.OBJLoader; -import com.jme3.system.NativeLibraryLoader; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import vhacd.VHACDParameters; - -/** - * A console application to generate the collision-shape assets "teapot.j3o" and - * "teapotGi.j3o". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeTeapot { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeTeapot.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeTeapot() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeTeapot application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - Logger.getLogger(OBJLoader.class.getName()).setLevel(Level.SEVERE); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate the collision shape. - makeTeapot(); - } - // ************************************************************************* - // private methods - - /** - * Generate a collision shape for a teapot. - */ - private static void makeTeapot() { - DesktopAssetManager assetManager = new DesktopAssetManager(); - assetManager.registerLoader(OBJLoader.class, "obj"); - assetManager.registerLoader(MTLLoader.class, "mtl"); - assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); - assetManager.registerLocator(null, ClasspathLocator.class); - /* - * Import the Utah Teapot model (by Martin Newell and Jim Blinn) - * from jme3-testdata-3.1.0-stable.jar. - */ - String objAssetPath = "Models/Teapot/Teapot.obj"; - Spatial geom = assetManager.loadModel(objAssetPath); - - // Translate and uniformly scale the model to fit inside a 2x2x2 cube. - geom.setLocalTransform(Transform.IDENTITY); - Vector3f[] minMax = MySpatial.findMinMaxCoords(geom); - Vector3f center = MyVector3f.midpoint(minMax[0], minMax[1], null); - Vector3f offset = center.negate(); - geom.setLocalTranslation(offset); - - Node parent = new Node(); - parent.attachChild(geom); - Vector3f extents = minMax[1].subtract(minMax[0]); - float radius = MyMath.max(extents.x, extents.y, extents.z) / 2f; - parent.setLocalScale(1f / radius); - - Node cgmRoot = new Node(); - cgmRoot.attachChild(parent); - - // Using V-HACD, generate a CollisionShape to approximate the Mesh. - VHACDParameters parameters = new VHACDParameters(); - CompoundCollisionShape shape = ShapeUtils.createVhacdShape( - cgmRoot, parameters, "MakeTeapot"); - - // Write the shape to the asset file. - String assetPath = "CollisionShapes/teapot.j3o"; - String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, shape); - - // Generate a CollisionShape using GImpact and write to asset file. - Savable giShape = CollisionShapeFactory.createGImpactShape(cgmRoot); - assetPath = "CollisionShapes/teapotGi.j3o"; - writeFilePath = String.format("%s/%s", assetDirPath, assetPath); - Heart.writeJ3O(writeFilePath, giShape); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.export.Savable; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.MTLLoader; +import com.jme3.scene.plugins.OBJLoader; +import com.jme3.system.NativeLibraryLoader; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import vhacd.VHACDParameters; + +/** + * A console application to generate the collision-shape assets "teapot.j3o" and + * "teapotGi.j3o". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeTeapot { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeTeapot.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeTeapot() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeTeapot application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + Logger.getLogger(OBJLoader.class.getName()).setLevel(Level.SEVERE); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate the collision shape. + makeTeapot(); + } + // ************************************************************************* + // private methods + + /** + * Generate a collision shape for a teapot. + */ + private static void makeTeapot() { + DesktopAssetManager assetManager = new DesktopAssetManager(); + assetManager.registerLoader(OBJLoader.class, "obj"); + assetManager.registerLoader(MTLLoader.class, "mtl"); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + assetManager.registerLocator(null, ClasspathLocator.class); + /* + * Import the Utah Teapot model (by Martin Newell and Jim Blinn) + * from jme3-testdata-3.1.0-stable.jar. + */ + String objAssetPath = "Models/Teapot/Teapot.obj"; + Spatial geom = assetManager.loadModel(objAssetPath); + + // Translate and uniformly scale the model to fit inside a 2x2x2 cube. + geom.setLocalTransform(Transform.IDENTITY); + Vector3f[] minMax = MySpatial.findMinMaxCoords(geom); + Vector3f center = MyVector3f.midpoint(minMax[0], minMax[1], null); + Vector3f offset = center.negate(); + geom.setLocalTranslation(offset); + + Node parent = new Node(); + parent.attachChild(geom); + Vector3f extents = minMax[1].subtract(minMax[0]); + float radius = MyMath.max(extents.x, extents.y, extents.z) / 2f; + parent.setLocalScale(1f / radius); + + Node cgmRoot = new Node(); + cgmRoot.attachChild(parent); + + // Using V-HACD, generate a CollisionShape to approximate the Mesh. + VHACDParameters parameters = new VHACDParameters(); + CompoundCollisionShape shape = ShapeUtils.createVhacdShape( + cgmRoot, parameters, "MakeTeapot"); + + // Write the shape to the asset file. + String assetPath = "CollisionShapes/teapot.j3o"; + String writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, shape); + + // Generate a CollisionShape using GImpact and write to asset file. + Savable giShape = CollisionShapeFactory.createGImpactShape(cgmRoot); + assetPath = "CollisionShapes/teapotGi.j3o"; + writeFilePath = String.format("%s/%s", assetDirPath, assetPath); + Heart.writeJ3O(writeFilePath, giShape); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ProgressListener.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ProgressListener.java index 6e688d59f..24bfe62cd 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ProgressListener.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ProgressListener.java @@ -1,90 +1,90 @@ -/* - Copyright (c) 2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import java.util.logging.Logger; -import vhacd.VHACDProgressListener; - -/** - * A simple progress listener for V-HACD. - * - * @author Stephen Gold sgold@sonic.net - */ -class ProgressListener implements VHACDProgressListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(ProgressListener.class.getName()); - // ************************************************************************* - // fields - - /** - * overall completion percentage as of the latest update - */ - private double lastOP = -1.0; - /** - * text printed at the start of each output line - */ - final private String prefix; - // ************************************************************************* - // constructors - - /** - * Instantiate a listener for the specified prefix. - * - * @param prefix the prefix for each line of output - */ - ProgressListener(String prefix) { - this.prefix = prefix; - } - // ************************************************************************* - // VHACDProgressListener methods - - /** - * Callback invoked (by native code) for progress updates. - * - * @param overallPercent an overall completion percentage (≥0, ≤100) - * @param stagePercent a completion percentage for the current stage (≥0, - * ≤100) - * @param operationPercent a completion percentage for the current operation - * (≥0, ≤100) - * @param stageName the name of the current stage - * @param operationName the name of the current operation - */ - @Override - public void update(double overallPercent, double stagePercent, - double operationPercent, String stageName, String operationName) { - if (overallPercent != lastOP) { - System.out.printf("%s %.0f%% complete%n", prefix, overallPercent); - this.lastOP = overallPercent; - } - } -} +/* + Copyright (c) 2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import java.util.logging.Logger; +import vhacd.VHACDProgressListener; + +/** + * A simple progress listener for V-HACD. + * + * @author Stephen Gold sgold@sonic.net + */ +class ProgressListener implements VHACDProgressListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(ProgressListener.class.getName()); + // ************************************************************************* + // fields + + /** + * overall completion percentage as of the latest update + */ + private double lastOP = -1.0; + /** + * text printed at the start of each output line + */ + final private String prefix; + // ************************************************************************* + // constructors + + /** + * Instantiate a listener for the specified prefix. + * + * @param prefix the prefix for each line of output + */ + ProgressListener(String prefix) { + this.prefix = prefix; + } + // ************************************************************************* + // VHACDProgressListener methods + + /** + * Callback invoked (by native code) for progress updates. + * + * @param overallPercent an overall completion percentage (≥0, ≤100) + * @param stagePercent a completion percentage for the current stage (≥0, + * ≤100) + * @param operationPercent a completion percentage for the current operation + * (≥0, ≤100) + * @param stageName the name of the current stage + * @param operationName the name of the current operation + */ + @Override + public void update(double overallPercent, double stagePercent, + double operationPercent, String stageName, String operationName) { + if (overallPercent != lastOP) { + System.out.printf("%s %.0f%% complete%n", prefix, overallPercent); + this.lastOP = overallPercent; + } + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ShapeUtils.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ShapeUtils.java index dc097cc57..b03cb9a4c 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ShapeUtils.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/ShapeUtils.java @@ -1,140 +1,140 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shapes; - -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.scene.Spatial; -import java.util.logging.Logger; -import vhacd.VHACD; -import vhacd.VHACDParameters; -import vhacd4.Vhacd4; -import vhacd4.Vhacd4Parameters; - -/** - * Utility methods to generate collision shapes. - * - * @author Stephen Gold sgold@sonic.net - */ -final class ShapeUtils { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(ShapeUtils.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private ShapeUtils() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Create a collision shape using classic V-HACD and print statistics. - * - * @param modelRoot the model on which to base the shape (not null, - * unaffected) - * @param parameters (not null, unaffected) - * @param prefix the prefix for each line of output - * @return a new compound shape - */ - static CompoundCollisionShape createVhacdShape( - Spatial modelRoot, VHACDParameters parameters, String prefix) { - System.out.println(parameters); - VHACD.addProgressListener(new ProgressListener(prefix)); - - long startTime = System.nanoTime(); - CompoundCollisionShape result = CollisionShapeFactory.createVhacdShape( - modelRoot, parameters, null); - long elapsedNanoseconds = System.nanoTime() - startTime; - - printSummary(result, elapsedNanoseconds); - - return result; - } - - /** - * Create a collision shape using V-HACD v4 and print statistics. - * - * @param modelRoot the model on which to base the shape (not null, - * unaffected) - * @param parameters (not null, unaffected) - * @param prefix the prefix for each line of output - * @return a new compound shape - */ - static CompoundCollisionShape createVhacdShape( - Spatial modelRoot, Vhacd4Parameters parameters, String prefix) { - System.out.println(parameters); - Vhacd4.addProgressListener(new ProgressListener(prefix)); - - long startTime = System.nanoTime(); - CompoundCollisionShape result = CollisionShapeFactory.createVhacdShape( - modelRoot, parameters, null); - long elapsedNanoseconds = System.nanoTime() - startTime; - - printSummary(result, elapsedNanoseconds); - - return result; - } - - /** - * Print a summary of a compound hull shape to System.out and also check for - * V-HACD failure. - * - * @param result the generated collision shape (not null, unaffected) - * @param nanoseconds the time spent generating the shape (in nanoseconds, - * ≥0) - */ - private static void printSummary( - CompoundCollisionShape result, long nanoseconds) { - int numChildren = result.countChildren(); - if (numChildren == 0) { - throw new RuntimeException("V-HACD failed!"); - } - - int numVertices = 0; - ChildCollisionShape[] children = result.listChildren(); - for (ChildCollisionShape child : children) { - CollisionShape childShape = child.getShape(); - HullCollisionShape hull = (HullCollisionShape) childShape; - numVertices += hull.countHullVertices(); - } - System.out.printf(" number of hulls = %d (%.3f sec, %d vertices)%n", - numChildren, nanoseconds * 1e-9f, numVertices); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shapes; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.scene.Spatial; +import java.util.logging.Logger; +import vhacd.VHACD; +import vhacd.VHACDParameters; +import vhacd4.Vhacd4; +import vhacd4.Vhacd4Parameters; + +/** + * Utility methods to generate collision shapes. + * + * @author Stephen Gold sgold@sonic.net + */ +final class ShapeUtils { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(ShapeUtils.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private ShapeUtils() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Create a collision shape using classic V-HACD and print statistics. + * + * @param modelRoot the model on which to base the shape (not null, + * unaffected) + * @param parameters (not null, unaffected) + * @param prefix the prefix for each line of output + * @return a new compound shape + */ + static CompoundCollisionShape createVhacdShape( + Spatial modelRoot, VHACDParameters parameters, String prefix) { + System.out.println(parameters); + VHACD.addProgressListener(new ProgressListener(prefix)); + + long startTime = System.nanoTime(); + CompoundCollisionShape result = CollisionShapeFactory.createVhacdShape( + modelRoot, parameters, null); + long elapsedNanoseconds = System.nanoTime() - startTime; + + printSummary(result, elapsedNanoseconds); + + return result; + } + + /** + * Create a collision shape using V-HACD v4 and print statistics. + * + * @param modelRoot the model on which to base the shape (not null, + * unaffected) + * @param parameters (not null, unaffected) + * @param prefix the prefix for each line of output + * @return a new compound shape + */ + static CompoundCollisionShape createVhacdShape( + Spatial modelRoot, Vhacd4Parameters parameters, String prefix) { + System.out.println(parameters); + Vhacd4.addProgressListener(new ProgressListener(prefix)); + + long startTime = System.nanoTime(); + CompoundCollisionShape result = CollisionShapeFactory.createVhacdShape( + modelRoot, parameters, null); + long elapsedNanoseconds = System.nanoTime() - startTime; + + printSummary(result, elapsedNanoseconds); + + return result; + } + + /** + * Print a summary of a compound hull shape to System.out and also check for + * V-HACD failure. + * + * @param result the generated collision shape (not null, unaffected) + * @param nanoseconds the time spent generating the shape (in nanoseconds, + * ≥0) + */ + private static void printSummary( + CompoundCollisionShape result, long nanoseconds) { + int numChildren = result.countChildren(); + if (numChildren == 0) { + throw new RuntimeException("V-HACD failed!"); + } + + int numVertices = 0; + ChildCollisionShape[] children = result.listChildren(); + for (ChildCollisionShape child : children) { + CollisionShape childShape = child.getShape(); + HullCollisionShape hull = (HullCollisionShape) childShape; + numVertices += hull.countHullVertices(); + } + System.out.printf(" number of hulls = %d (%.3f sec, %d vertices)%n", + numChildren, nanoseconds * 1e-9f, numVertices); + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/package-info.java b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/package-info.java index 99babee7b..abe54e69e 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/package-info.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/shapes/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2019, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes to generate collision shapes for use in MinieExamples. - */ -package jme3utilities.minie.test.shapes; +/* + Copyright (c) 2019, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes to generate collision shapes for use in MinieExamples. + */ +package jme3utilities.minie.test.shapes; diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakeGreenTile.java b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakeGreenTile.java index 1c71c2b77..829b8f007 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakeGreenTile.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakeGreenTile.java @@ -1,120 +1,120 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.textures; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; - -/** - * A console application to generate the texture "greenTile.png". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakeGreenTile { - // ************************************************************************* - // constants and loggers - - /** - * size of the texture map (pixels per side) - */ - final private static int textureSize = 64; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakeGreenTile.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakeGreenTile() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakeGreenTile application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate color image map. - makeGreenTile(); - } - // ************************************************************************* - // private methods - - /** - * Generate an image map for a green square with a dark border. - */ - private static void makeGreenTile() { - // Create a blank, color, buffered image for the texture map. - BufferedImage image = new BufferedImage(textureSize, textureSize, - BufferedImage.TYPE_4BYTE_ABGR); - Graphics2D graphics = image.createGraphics(); - - float opacity = 1f; - Color dark = new Color(0.1f, 0.1f, 0.1f, opacity); - graphics.setColor(dark); - graphics.fillRect(0, 0, textureSize, textureSize); - - Color green = new Color(0f, 0.4f, 0f, opacity); - graphics.setColor(green); - graphics.fillRect(4, 4, textureSize - 8, textureSize - 8); - - // Write the image to the asset file. - String assetPath = "Textures/greenTile.png"; - String filePath = String.format("%s/%s", assetDirPath, assetPath); - try { - Heart.writeImage(filePath, image); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.textures; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; + +/** + * A console application to generate the texture "greenTile.png". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakeGreenTile { + // ************************************************************************* + // constants and loggers + + /** + * size of the texture map (pixels per side) + */ + final private static int textureSize = 64; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakeGreenTile.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakeGreenTile() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakeGreenTile application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate color image map. + makeGreenTile(); + } + // ************************************************************************* + // private methods + + /** + * Generate an image map for a green square with a dark border. + */ + private static void makeGreenTile() { + // Create a blank, color, buffered image for the texture map. + BufferedImage image = new BufferedImage(textureSize, textureSize, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D graphics = image.createGraphics(); + + float opacity = 1f; + Color dark = new Color(0.1f, 0.1f, 0.1f, opacity); + graphics.setColor(dark); + graphics.fillRect(0, 0, textureSize, textureSize); + + Color green = new Color(0f, 0.4f, 0f, opacity); + graphics.setColor(green); + graphics.fillRect(4, 4, textureSize - 8, textureSize - 8); + + // Write the image to the asset file. + String assetPath = "Textures/greenTile.png"; + String filePath = String.format("%s/%s", assetDirPath, assetPath); + try { + Heart.writeImage(filePath, image); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePlaid.java b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePlaid.java index 7ccc29878..c0e26e674 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePlaid.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePlaid.java @@ -1,127 +1,127 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.textures; - -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; - -/** - * A console application to generate the texture "plaid.png". - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakePlaid { - // ************************************************************************* - // constants and loggers - - /** - * size of the texture map (pixels per side) - */ - final private static int textureSize = 64; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakePlaid.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakePlaid() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakePlaid application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate color image map. - makePlaid(); - } - // ************************************************************************* - // private methods - - /** - * Generate an image map for a simple red-and-white plaid. - */ - private static void makePlaid() { - // Create a blank, color buffered image for the texture map. - BufferedImage image = new BufferedImage(textureSize, textureSize, - BufferedImage.TYPE_4BYTE_ABGR); - Graphics2D graphics = image.createGraphics(); - - float brightness = 1f; - float halfBright = brightness / 2f; - float opacity = 1f; - Color pink = new Color(brightness, halfBright, halfBright, opacity); - Color red = new Color(brightness, 0f, 0f, opacity); - Color white = new Color(brightness, brightness, brightness, opacity); - - int halfSize = textureSize / 2; - graphics.setColor(white); - graphics.fillRect(0, 0, halfSize, halfSize); - graphics.setColor(pink); - graphics.fillRect(halfSize, 0, halfSize, halfSize); - graphics.fillRect(0, halfSize, halfSize, halfSize); - graphics.setColor(red); - graphics.fillRect(halfSize, halfSize, halfSize, halfSize); - - // Write the image to the asset file. - String assetPath = "Textures/plaid.png"; - String filePath = String.format("%s/%s", assetDirPath, assetPath); - try { - Heart.writeImage(filePath, image); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.textures; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; + +/** + * A console application to generate the texture "plaid.png". + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakePlaid { + // ************************************************************************* + // constants and loggers + + /** + * size of the texture map (pixels per side) + */ + final private static int textureSize = 64; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakePlaid.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakePlaid() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakePlaid application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate color image map. + makePlaid(); + } + // ************************************************************************* + // private methods + + /** + * Generate an image map for a simple red-and-white plaid. + */ + private static void makePlaid() { + // Create a blank, color buffered image for the texture map. + BufferedImage image = new BufferedImage(textureSize, textureSize, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D graphics = image.createGraphics(); + + float brightness = 1f; + float halfBright = brightness / 2f; + float opacity = 1f; + Color pink = new Color(brightness, halfBright, halfBright, opacity); + Color red = new Color(brightness, 0f, 0f, opacity); + Color white = new Color(brightness, brightness, brightness, opacity); + + int halfSize = textureSize / 2; + graphics.setColor(white); + graphics.fillRect(0, 0, halfSize, halfSize); + graphics.setColor(pink); + graphics.fillRect(halfSize, 0, halfSize, halfSize); + graphics.fillRect(0, halfSize, halfSize, halfSize); + graphics.setColor(red); + graphics.fillRect(halfSize, halfSize, halfSize, halfSize); + + // Write the image to the asset file. + String assetPath = "Textures/plaid.png"; + String filePath = String.format("%s/%s", assetDirPath, assetPath); + try { + Heart.writeImage(filePath, image); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePoolBalls.java b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePoolBalls.java index 522260402..753301796 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePoolBalls.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/MakePoolBalls.java @@ -1,267 +1,267 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.textures; - -import com.jme3.math.FastMath; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; - -/** - * A console application to generate 15 textures for pool balls. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MakePoolBalls { - // ************************************************************************* - // constants and loggers - - /** - * alpha component for an opaque Color - */ - final private static float opaque = 1f; - /** - * color for numerals and the 8 ball - */ - final private static Color black = new Color(0f, 0f, 0f, opaque); - /** - * color of the 2 ball and the 10 ball - */ - final private static Color blue = new Color(0f, 0f, 1f, opaque); - /** - * color of the 6 ball and the 14 ball - */ - final private static Color green = new Color(0f, 0.4f, 0f, opaque); - /** - * color of the 7 ball and the 15 ball - */ - final private static Color maroon = new Color(0.7f, 0.1f, 0.5f, opaque); - /** - * color of the 5 ball and the 13 ball - */ - final private static Color orange = new Color(1f, 0.5f, 0f, opaque); - /** - * color of the 3 ball and the 11 ball - */ - final private static Color red = new Color(1f, 0f, 0f, opaque); - /** - * color of the 4 ball and the 12 ball - */ - final private static Color violet = new Color(0.5f, 0f, 1f, opaque); - /** - * color for the stripe/spot backgrounds - */ - final private static Color white = new Color(1f, 1f, 1f, opaque); - /** - * color of the 1 ball and the 9 ball - */ - final private static Color yellow = new Color(0.8f, 0.8f, 0f, opaque); - /** - * height of the texture map (in pixels) - */ - final private static int textureHeight = 128; - /** - * width of the texture map (in pixels) - */ - final private static int textureWidth = 256; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(MakePoolBalls.class.getName()); - /** - * filesystem path to the asset directory/folder for output - */ - final private static String assetDirPath - = "../MinieExamples/src/main/resources"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MakePoolBalls() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MakePoolBalls application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers found in some imported packages. - Heart.setLoggingLevels(Level.WARNING); - - // Log the working directory. - String userDir = System.getProperty("user.dir"); - logger.log(Level.INFO, "working directory is {0}", - MyString.quote(userDir)); - - // Generate color image maps. - for (int ballId = 1; ballId <= 15; ++ballId) { - makePoolBall(ballId); - } - } - // ************************************************************************* - // private methods - - /** - * Generate the name of the identified pool ball. - * - * @param ballId which ball (8=eight ball, ≥1, ≤15) - * @return the name - */ - private static String ballName(int ballId) { - String result = ballId + "Ball"; - return result; - } - - /** - * Test whether a pixel is in the spot region of a pool ball. Tuned for - * TextureMode.Original. - * - * @param x the X coordinate of the pixel (≥0, <textureSize) - * @param y the Y coordinate of the pixel (≥0, <textureSize) - * @return true if in the spot region, otherwise false - */ - private static boolean inSpotRegion(int x, int y) { - float xx = FastMath.PI * (x + 0.5f) / textureWidth; // 0 -> pi - float yy = (y + 0.5f) / textureHeight; // 0 -> 1 - - final float xCenter1 = 1f; - final float xCenter2 = FastMath.HALF_PI + 1f; - final float yCenter = 0.5f; - final float r2 = 0.03f; - - if (MyMath.sumOfSquares(xx - xCenter1, yy - yCenter) < r2) { - return true; - } else if (MyMath.sumOfSquares(xx - xCenter2, yy - yCenter) < r2) { - return true; - } else { - return false; - } - } - - /** - * Test whether a pixel is in the stripe region of a pool ball. - * - * @param x the X coordinate of the pixel (≥0, <textureSize) - * @param y the Y coordinate of the pixel (≥0, <textureSize) - * @return true if in the stripe region, otherwise false - */ - private static boolean inStripeRegion(int x, int y) { - float yy = (y + 0.5f) / textureHeight; - - if (Math.abs(yy - 0.5f) < 0.25f) { - return true; - } else { - return false; - } - } - - /** - * Generate an image map for a single pool ball. - * - * @param ballId which ball (8=eight ball, ≥1, ≤15) - */ - private static void makePoolBall(int ballId) { - // Create a blank, color buffered image for the texture map. - BufferedImage image = new BufferedImage(textureWidth, textureHeight, - BufferedImage.TYPE_4BYTE_ABGR); - Graphics2D graphics = image.createGraphics(); - - Color color; - switch (ballId % 8) { - case 0: - color = black; - break; - case 1: - color = yellow; - break; - case 2: - color = blue; - break; - case 3: - color = red; - break; - case 4: - color = violet; - break; - case 5: - color = orange; - break; - case 6: - color = green; - break; - case 7: - color = maroon; - break; - default: - throw new IllegalArgumentException("ballId = " + ballId); - } - - // Draw the texture, one pixel at a time. - for (int x = 0; x < textureWidth; ++x) { - for (int y = 0; y < textureHeight; ++y) { - if (inSpotRegion(x, y)) { - graphics.setColor(white); - - } else if (ballId <= 8) { // solid pattern - graphics.setColor(color); - - } else { // stripe pattern - if (inStripeRegion(x, y)) { - graphics.setColor(color); - } else { - graphics.setColor(white); - } - } - - graphics.fillRect(x, y, 1, 1); - } - } - - // Write the image to the asset file. - String ballName = ballName(ballId); - String assetPath = "Textures/poolBalls/" + ballName + ".png"; - String filePath = String.format("%s/%s", assetDirPath, assetPath); - try { - Heart.writeImage(filePath, image); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.textures; + +import com.jme3.math.FastMath; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; + +/** + * A console application to generate 15 textures for pool balls. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MakePoolBalls { + // ************************************************************************* + // constants and loggers + + /** + * alpha component for an opaque Color + */ + final private static float opaque = 1f; + /** + * color for numerals and the 8 ball + */ + final private static Color black = new Color(0f, 0f, 0f, opaque); + /** + * color of the 2 ball and the 10 ball + */ + final private static Color blue = new Color(0f, 0f, 1f, opaque); + /** + * color of the 6 ball and the 14 ball + */ + final private static Color green = new Color(0f, 0.4f, 0f, opaque); + /** + * color of the 7 ball and the 15 ball + */ + final private static Color maroon = new Color(0.7f, 0.1f, 0.5f, opaque); + /** + * color of the 5 ball and the 13 ball + */ + final private static Color orange = new Color(1f, 0.5f, 0f, opaque); + /** + * color of the 3 ball and the 11 ball + */ + final private static Color red = new Color(1f, 0f, 0f, opaque); + /** + * color of the 4 ball and the 12 ball + */ + final private static Color violet = new Color(0.5f, 0f, 1f, opaque); + /** + * color for the stripe/spot backgrounds + */ + final private static Color white = new Color(1f, 1f, 1f, opaque); + /** + * color of the 1 ball and the 9 ball + */ + final private static Color yellow = new Color(0.8f, 0.8f, 0f, opaque); + /** + * height of the texture map (in pixels) + */ + final private static int textureHeight = 128; + /** + * width of the texture map (in pixels) + */ + final private static int textureWidth = 256; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(MakePoolBalls.class.getName()); + /** + * filesystem path to the asset directory/folder for output + */ + final private static String assetDirPath + = "../MinieExamples/src/main/resources"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MakePoolBalls() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MakePoolBalls application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers found in some imported packages. + Heart.setLoggingLevels(Level.WARNING); + + // Log the working directory. + String userDir = System.getProperty("user.dir"); + logger.log(Level.INFO, "working directory is {0}", + MyString.quote(userDir)); + + // Generate color image maps. + for (int ballId = 1; ballId <= 15; ++ballId) { + makePoolBall(ballId); + } + } + // ************************************************************************* + // private methods + + /** + * Generate the name of the identified pool ball. + * + * @param ballId which ball (8=eight ball, ≥1, ≤15) + * @return the name + */ + private static String ballName(int ballId) { + String result = ballId + "Ball"; + return result; + } + + /** + * Test whether a pixel is in the spot region of a pool ball. Tuned for + * TextureMode.Original. + * + * @param x the X coordinate of the pixel (≥0, <textureSize) + * @param y the Y coordinate of the pixel (≥0, <textureSize) + * @return true if in the spot region, otherwise false + */ + private static boolean inSpotRegion(int x, int y) { + float xx = FastMath.PI * (x + 0.5f) / textureWidth; // 0 -> pi + float yy = (y + 0.5f) / textureHeight; // 0 -> 1 + + final float xCenter1 = 1f; + final float xCenter2 = FastMath.HALF_PI + 1f; + final float yCenter = 0.5f; + final float r2 = 0.03f; + + if (MyMath.sumOfSquares(xx - xCenter1, yy - yCenter) < r2) { + return true; + } else if (MyMath.sumOfSquares(xx - xCenter2, yy - yCenter) < r2) { + return true; + } else { + return false; + } + } + + /** + * Test whether a pixel is in the stripe region of a pool ball. + * + * @param x the X coordinate of the pixel (≥0, <textureSize) + * @param y the Y coordinate of the pixel (≥0, <textureSize) + * @return true if in the stripe region, otherwise false + */ + private static boolean inStripeRegion(int x, int y) { + float yy = (y + 0.5f) / textureHeight; + + if (Math.abs(yy - 0.5f) < 0.25f) { + return true; + } else { + return false; + } + } + + /** + * Generate an image map for a single pool ball. + * + * @param ballId which ball (8=eight ball, ≥1, ≤15) + */ + private static void makePoolBall(int ballId) { + // Create a blank, color buffered image for the texture map. + BufferedImage image = new BufferedImage(textureWidth, textureHeight, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D graphics = image.createGraphics(); + + Color color; + switch (ballId % 8) { + case 0: + color = black; + break; + case 1: + color = yellow; + break; + case 2: + color = blue; + break; + case 3: + color = red; + break; + case 4: + color = violet; + break; + case 5: + color = orange; + break; + case 6: + color = green; + break; + case 7: + color = maroon; + break; + default: + throw new IllegalArgumentException("ballId = " + ballId); + } + + // Draw the texture, one pixel at a time. + for (int x = 0; x < textureWidth; ++x) { + for (int y = 0; y < textureHeight; ++y) { + if (inSpotRegion(x, y)) { + graphics.setColor(white); + + } else if (ballId <= 8) { // solid pattern + graphics.setColor(color); + + } else { // stripe pattern + if (inStripeRegion(x, y)) { + graphics.setColor(color); + } else { + graphics.setColor(white); + } + } + + graphics.fillRect(x, y, 1, 1); + } + } + + // Write the image to the asset file. + String ballName = ballName(ballId); + String assetPath = "Textures/poolBalls/" + ballName + ".png"; + String filePath = String.format("%s/%s", assetDirPath, assetPath); + try { + Heart.writeImage(filePath, image); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } +} diff --git a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/package-info.java b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/package-info.java index 468769e6c..7bd276498 100644 --- a/MinieAssets/src/main/java/jme3utilities/minie/test/textures/package-info.java +++ b/MinieAssets/src/main/java/jme3utilities/minie/test/textures/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2019-2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Procedurally generated textures for use in MinieExamples. - */ -package jme3utilities.minie.test.textures; +/* + Copyright (c) 2019-2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Procedurally generated textures for use in MinieExamples. + */ +package jme3utilities.minie.test.textures; diff --git a/MinieAssets/src/main/resources/Interface/Fonts/license.txt b/MinieAssets/src/main/resources/Interface/Fonts/license.txt index 59164de02..1c3ad8e8b 100644 --- a/MinieAssets/src/main/resources/Interface/Fonts/license.txt +++ b/MinieAssets/src/main/resources/Interface/Fonts/license.txt @@ -1,25 +1,25 @@ -Licensing history for assets in src/main/resources/Interface/Fonts - -Downloaded from http://tobiasjung.name/profont - -ProFont has been published under the MIT license: - -Copyright (c) 2014 Carl Osterwald, Stephen C. Gilardi, Andrew Welch - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +Licensing history for assets in src/main/resources/Interface/Fonts + +Downloaded from http://tobiasjung.name/profont + +ProFont has been published under the MIT license: + +Copyright (c) 2014 Carl Osterwald, Stephen C. Gilardi, Andrew Welch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/Ankh/license.txt b/MinieAssets/src/main/resources/Models/Ankh/license.txt index dad3930e8..1b9e6d0e8 100644 --- a/MinieAssets/src/main/resources/Models/Ankh/license.txt +++ b/MinieAssets/src/main/resources/Models/Ankh/license.txt @@ -1,7 +1,7 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/Ankh - -The Ankh model asset was created by Stephen Gold using Blender. - -The files "Ankh.blend" and "Ankh.glb" are licensed -under the Creative Commons CC0 1.0 Universal license +Licensing history for assets in MinieAssets/src/main/resources/Models/Ankh + +The Ankh model asset was created by Stephen Gold using Blender. + +The files "Ankh.blend" and "Ankh.glb" are licensed +under the Creative Commons CC0 1.0 Universal license (https://creativecommons.org/publicdomain/zero/1.0/). \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/Banana/license.txt b/MinieAssets/src/main/resources/Models/Banana/license.txt index 29567eee3..06f6f7f76 100644 --- a/MinieAssets/src/main/resources/Models/Banana/license.txt +++ b/MinieAssets/src/main/resources/Models/Banana/license.txt @@ -1,7 +1,7 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/Banana - -The Horseshoe model asset was created by Stephen Gold using Blender v2.81a. - -The files "Banana.blend" and "Banana.glb" are licensed -under the Creative Commons CC0 1.0 Universal license +Licensing history for assets in MinieAssets/src/main/resources/Models/Banana + +The Horseshoe model asset was created by Stephen Gold using Blender v2.81a. + +The files "Banana.blend" and "Banana.glb" are licensed +under the Creative Commons CC0 1.0 Universal license (https://creativecommons.org/publicdomain/zero/1.0/). \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/Barrel/license.txt b/MinieAssets/src/main/resources/Models/Barrel/license.txt index 9969b481c..583b418b8 100644 --- a/MinieAssets/src/main/resources/Models/Barrel/license.txt +++ b/MinieAssets/src/main/resources/Models/Barrel/license.txt @@ -1,7 +1,7 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/Barrel - -The Barrel model asset was created by Stephen Gold using Blender. - -The files "Barrel.blend" and "Barrel.glb" are licensed -under the Creative Commons CC0 1.0 Universal license +Licensing history for assets in MinieAssets/src/main/resources/Models/Barrel + +The Barrel model asset was created by Stephen Gold using Blender. + +The files "Barrel.blend" and "Barrel.glb" are licensed +under the Creative Commons CC0 1.0 Universal license (https://creativecommons.org/publicdomain/zero/1.0/). \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/BowlingPin/license.txt b/MinieAssets/src/main/resources/Models/BowlingPin/license.txt index c95396c2d..537e5e331 100644 --- a/MinieAssets/src/main/resources/Models/BowlingPin/license.txt +++ b/MinieAssets/src/main/resources/Models/BowlingPin/license.txt @@ -1,7 +1,7 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/BowlingPin - -The Horseshoe model asset was created by Stephen Gold using Blender v2.81a. - -The files "BowlingPin.blend" and "BowlingPin.glb" are licensed -under the Creative Commons CC0 1.0 Universal license +Licensing history for assets in MinieAssets/src/main/resources/Models/BowlingPin + +The Horseshoe model asset was created by Stephen Gold using Blender v2.81a. + +The files "BowlingPin.blend" and "BowlingPin.glb" are licensed +under the Creative Commons CC0 1.0 Universal license (https://creativecommons.org/publicdomain/zero/1.0/). \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/CandyDish/license.txt b/MinieAssets/src/main/resources/Models/CandyDish/license.txt index 3075c263d..17c3a2c87 100644 --- a/MinieAssets/src/main/resources/Models/CandyDish/license.txt +++ b/MinieAssets/src/main/resources/Models/CandyDish/license.txt @@ -1,7 +1,7 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/CandyDish - -The CandyDish model asset was created by Stephen Gold using Blender v2.81a. - -The files "CandyDish.blend" and "CandyDish.glb" are licensed -under the Creative Commons CC0 1.0 Universal license +Licensing history for assets in MinieAssets/src/main/resources/Models/CandyDish + +The CandyDish model asset was created by Stephen Gold using Blender v2.81a. + +The files "CandyDish.blend" and "CandyDish.glb" are licensed +under the Creative Commons CC0 1.0 Universal license (https://creativecommons.org/publicdomain/zero/1.0/). \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/Horseshoe/license.txt b/MinieAssets/src/main/resources/Models/Horseshoe/license.txt index 3f0499e7d..e17decf37 100644 --- a/MinieAssets/src/main/resources/Models/Horseshoe/license.txt +++ b/MinieAssets/src/main/resources/Models/Horseshoe/license.txt @@ -1,7 +1,7 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/Horseshoe - -The Horseshoe model asset was created by Stephen Gold using Blender v2.81a. - -The files "Horseshoe.blend" and "Horseshoe.glb" are licensed -under the Creative Commons CC0 1.0 Universal license +Licensing history for assets in MinieAssets/src/main/resources/Models/Horseshoe + +The Horseshoe model asset was created by Stephen Gold using Blender v2.81a. + +The files "Horseshoe.blend" and "Horseshoe.glb" are licensed +under the Creative Commons CC0 1.0 Universal license (https://creativecommons.org/publicdomain/zero/1.0/). \ No newline at end of file diff --git a/MinieAssets/src/main/resources/Models/MhGame/license.txt b/MinieAssets/src/main/resources/Models/MhGame/license.txt index 3bf0bfd06..8bf92b991 100644 --- a/MinieAssets/src/main/resources/Models/MhGame/license.txt +++ b/MinieAssets/src/main/resources/Models/MhGame/license.txt @@ -1,21 +1,21 @@ -Licensing history for assets in MinieAssets/src/main/resources/Models/MhGame and -MinieAssets/src/main/resources/Models/MhGame/textures - -The MhGame model asset was created by Stephen Gold using the export -functionality of an official and unmodified version (v1.1.1) of MakeHuman. - -The files "MhGame.material", "MhGame.mesh.xml", "MhGame.skeleton.xml", -"brown_eye.png", "eyebrow001.png", "lit_matte.png", "lit_standard_skin.png", -"male_worksuit01_ao.png", "male_worksuit01_diffuse.png", -"male_worksuit01_normal.png", "shoes03_diffuse.png", "skinmat_eye.png", -"young_lightskinned_male_diffuse2.png" are therefore dual licensed under both -(1) the GNU Affero General Public License (http://www.gnu.org/licenses/) -and (2) Creative Commons CC0 1.0 Universal license -(https://creativecommons.org/publicdomain/zero/1.0/). - -Stephen Gold affirms that, to the extent of his rights to the files listed -above, he voluntarily elects to apply the Creative Commons CC0 1.0 Universal -license. - -For more information about these license terms -see http://www.makehuman.org/content/license_explanation.html +Licensing history for assets in MinieAssets/src/main/resources/Models/MhGame and +MinieAssets/src/main/resources/Models/MhGame/textures + +The MhGame model asset was created by Stephen Gold using the export +functionality of an official and unmodified version (v1.1.1) of MakeHuman. + +The files "MhGame.material", "MhGame.mesh.xml", "MhGame.skeleton.xml", +"brown_eye.png", "eyebrow001.png", "lit_matte.png", "lit_standard_skin.png", +"male_worksuit01_ao.png", "male_worksuit01_diffuse.png", +"male_worksuit01_normal.png", "shoes03_diffuse.png", "skinmat_eye.png", +"young_lightskinned_male_diffuse2.png" are therefore dual licensed under both +(1) the GNU Affero General Public License (http://www.gnu.org/licenses/) +and (2) Creative Commons CC0 1.0 Universal license +(https://creativecommons.org/publicdomain/zero/1.0/). + +Stephen Gold affirms that, to the extent of his rights to the files listed +above, he voluntarily elects to apply the Creative Commons CC0 1.0 Universal +license. + +For more information about these license terms +see http://www.makehuman.org/content/license_explanation.html diff --git a/MinieDump/build.gradle b/MinieDump/build.gradle index 932da7698..dc6bdb722 100644 --- a/MinieDump/build.gradle +++ b/MinieDump/build.gradle @@ -1,25 +1,25 @@ -// Note: "common.gradle" in the root project contains additional initialization -// for this project. This initialization is applied in the "build.gradle" -// of the root project. - -plugins { - id 'application' -} - -tasks.withType(JavaCompile) { // Java compile-time options: - options.deprecation = true -} - -application { - mainClass = 'jme3utilities.minie.cli.MinieDump' -} -jar.manifest.attributes('Main-Class': application.mainClass) - -dependencies { - implementation desktopCoordinates - implementation heartCoordinates - runtimeOnly pluginsCoordinates - - //implementation 'com.github.stephengold:Minie:' + minieVersion // for published library - implementation project(':MinieLibrary') // for latest sourcecode -} +// Note: "common.gradle" in the root project contains additional initialization +// for this project. This initialization is applied in the "build.gradle" +// of the root project. + +plugins { + id 'application' +} + +tasks.withType(JavaCompile) { // Java compile-time options: + options.deprecation = true +} + +application { + mainClass = 'jme3utilities.minie.cli.MinieDump' +} +jar.manifest.attributes('Main-Class': application.mainClass) + +dependencies { + implementation desktopCoordinates + implementation heartCoordinates + runtimeOnly pluginsCoordinates + + //implementation 'com.github.stephengold:Minie:' + minieVersion // for published library + implementation project(':MinieLibrary') // for latest sourcecode +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/BuoyDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/BuoyDemo.java index 240db0b6e..b68d0a013 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/BuoyDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/BuoyDemo.java @@ -1,689 +1,689 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.anim.AnimComposer; -import com.jme3.anim.SkinningControl; -import com.jme3.anim.util.AnimMigrationUtils; -import com.jme3.app.Application; -import com.jme3.app.StatsAppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import com.jme3.system.AppSettings; -import com.jme3.util.SkyFactory; -import com.jme3.water.SimpleWaterProcessor; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InfluenceUtil; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.debug.SkeletonVisualizer; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.controllers.BuoyController; -import jme3utilities.minie.test.tunings.BaseMeshControl; -import jme3utilities.minie.test.tunings.ElephantControl; -import jme3utilities.minie.test.tunings.JaimeControl; -import jme3utilities.minie.test.tunings.MhGameControl; -import jme3utilities.minie.test.tunings.NinjaControl; -import jme3utilities.minie.test.tunings.OtoControl; -import jme3utilities.minie.test.tunings.PuppetControl; -import jme3utilities.minie.test.tunings.SinbadControl; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; - -/** - * Demonstrate BuoyController. - *

- * Seen in the March 2019 demo video: - * https://www.youtube.com/watch?v=eq09m7pbk5A - * - * @author Stephen Gold sgold@sonic.net - */ -public class BuoyDemo extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * Y coordinate of the water's surface (in world coordinates) - */ - final public static float surfaceElevation = 0f; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(BuoyDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = BuoyDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * Control being tested - */ - private static DynamicAnimControl dac; - /** - * root node of the C-G model on which the Control is being tested - */ - private static Node cgModel; - /** - * scene-graph subtree containing all geometries visible in reflections - */ - final private static Node reflectiblesNode = new Node("reflectibles"); - /** - * scene-graph subtree containing all reflective geometries - */ - final private static Node reflectorsNode = new Node("reflectors"); - /** - * scene processor for water effects - */ - private static SimpleWaterProcessor processor; - /** - * visualizer for the skeleton of the C-G model - */ - private static SkeletonVisualizer sv; - /** - * name of the Animation/Action to play on the C-G model - */ - private static String animationName = null; - // ************************************************************************* - // constructors - - /** - * Instantiate the BuoyDemo application. - */ - public BuoyDemo() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the BuoyDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new BuoyDemo(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - super.acorusInit(); - - rootNode.attachChild(reflectiblesNode); - rootNode.attachChild(reflectorsNode); - - configureCamera(); - configureDumper(); - configurePhysics(); - addLighting(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - addSurface(); - addSky(); - addModel("Sinbad"); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - super.configureDumper(); - - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.JointsInSpaces, true); - } - - /** - * Calculate screen bounds for the detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes along the top edge of the viewport. - float margin = 10f; // in pixels - float width = viewPortWidth - 2f * margin; - float height = viewPortHeight - 2f * margin; - float leftX = margin; - float topY = margin + height; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - dim.bind(asDumpScenes, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind("go floating", KeyInput.KEY_0, KeyInput.KEY_SPACE); - - dim.bind("load BaseMesh", KeyInput.KEY_F11); - dim.bind("load Elephant", KeyInput.KEY_F3); - dim.bind("load Jaime", KeyInput.KEY_F2); - dim.bind("load MhGame", KeyInput.KEY_F9); - dim.bind("load Ninja", KeyInput.KEY_F7); - dim.bind("load Oto", KeyInput.KEY_F6); - dim.bind("load Puppet", KeyInput.KEY_F8); - dim.bind("load Sinbad", KeyInput.KEY_F1); - dim.bind("load SinbadWith1Sword", KeyInput.KEY_F10); - dim.bind("load SinbadWithSwords", KeyInput.KEY_F4); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("rotateLeft", KeyInput.KEY_LEFT); - dim.bindSignal("rotateRight", KeyInput.KEY_RIGHT); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleDebug, KeyInput.KEY_SLASH); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind("toggle meshes", KeyInput.KEY_M); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind("toggle skeleton", KeyInput.KEY_V); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "go floating": - goFloating(); - return; - - case "toggle meshes": - toggleMeshes(); - return; - case "toggle skeleton": - toggleSkeleton(); - return; - default: - } - - String[] words = actionString.split(" "); - if (words.length == 2 && "load".equals(words[0])) { - addModel(words[1]); - return; - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - Signals signals = getSignals(); - - float rotateAngle = 0f; - if (signals.test("rotateRight")) { - rotateAngle += tpf; - } - if (signals.test("rotateLeft")) { - rotateAngle -= tpf; - } - if (rotateAngle != 0f) { - rotateAngle /= speed; - Quaternion orientation = MySpatial.worldOrientation(cgModel, null); - Quaternion rotate - = new Quaternion().fromAngles(0f, rotateAngle, 0f); - rotate.mult(orientation, orientation); - MySpatial.setWorldOrientation(cgModel, orientation); - } - } - // ************************************************************************* - // private methods - - /** - * Add lighting and reflections to the main scene. - */ - private void addLighting() { - ColorRGBA ambientColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootNode.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(-1f, -0.2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - sun.setName("sun"); - - processor = new SimpleWaterProcessor(assetManager); - viewPort.addProcessor(processor); - processor.setLightPosition(direction.mult(1000f)); - Plane surface = new Plane(Vector3f.UNIT_Y, surfaceElevation); - processor.setPlane(surface); - processor.setReflectionScene(reflectiblesNode); - - // Clip everything below the surface. - processor.setReflectionClippingOffset(-0.1f); - - // Configure water and wave parameters. - float waveHeight = 0.2f; - processor.setDistortionScale(waveHeight); - float waterTransparency = 0.4f; - processor.setWaterDepth(waterTransparency); - float waveSpeed = 0.06f; - processor.setWaveSpeed(waveSpeed); - } - - /** - * Add an animated model to the scene, removing any previously added model. - * - * @param modelName the name of the model to add (not null, not empty) - */ - private void addModel(String modelName) { - if (cgModel != null) { - dac.getSpatial().removeControl(dac); - reflectiblesNode.detachChild(cgModel); - rootNode.removeControl(sv); - } - - switch (modelName) { - case "BaseMesh": - loadBaseMesh(); - break; - case "Elephant": - loadElephant(); - break; - case "Jaime": - loadJaime(); - break; - case "MhGame": - loadMhGame(); - break; - case "Ninja": - loadNinja(); - break; - case "Oto": - loadOto(); - break; - case "Puppet": - loadPuppet(); - break; - case "Sinbad": - loadSinbad(); - break; - case "SinbadWith1Sword": - loadSinbadWith1Sword(); - break; - case "SinbadWithSwords": - loadSinbadWithSwords(); - break; - default: - throw new IllegalArgumentException(modelName); - } - - List list = MySpatial.listSpatials(cgModel); - for (Spatial spatial : list) { - spatial.setShadowMode(RenderQueue.ShadowMode.Cast); - } - cgModel.setCullHint(Spatial.CullHint.Never); - - reflectiblesNode.attachChild(cgModel); - setCgmHeight(cgModel, 10f); - centerCgm(cgModel); - - SkinningControl sc = (SkinningControl) RagUtils.findSControl(cgModel); - Spatial controlledSpatial = sc.getSpatial(); - - controlledSpatial.addControl(dac); - dac.setGravity(new Vector3f(0f, -50f, 0f)); - PhysicsSpace physicsSpace = getPhysicsSpace(); - dac.setPhysicsSpace(physicsSpace); - - // Add buoyancy to each BoneLink. - List links = dac.listLinks(PhysicsLink.class); - float density = 1.5f; - for (PhysicsLink link : links) { - BuoyController buoy - = new BuoyController(link, density, surfaceElevation); - link.addIKController(buoy); - } - - AnimComposer composer - = controlledSpatial.getControl(AnimComposer.class); - composer.setCurrentAction(animationName); - - sv = new SkeletonVisualizer(assetManager, sc); - sv.setLineColor(ColorRGBA.Yellow); - InfluenceUtil.hideNonInfluencers(sv, sc); - rootNode.addControl(sv); - - if (isPaused()) { - togglePause(); - } - } - - /** - * Add a cube-mapped sky. - */ - private void addSky() { - String assetPath = "Textures/Sky/Bright/BrightSky.dds"; - Spatial sky = SkyFactory.createSky( - assetManager, assetPath, SkyFactory.EnvMapType.CubeMap); - reflectiblesNode.attachChild(sky); - } - - /** - * Add a large Quad to represent the surface of the water. - */ - private static void addSurface() { - float diameter = 2000f; - Mesh mesh = new Quad(diameter, diameter); - mesh.scaleTextureCoordinates(new Vector2f(80f, 80f)); - - Geometry geometry = new Geometry("water surface", mesh); - reflectorsNode.attachChild(geometry); - - geometry.move(-diameter / 2, 0f, diameter / 2); - Quaternion rot = new Quaternion(); - rot.fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X); - geometry.setLocalRotation(rot); - Material material = processor.getMaterial(); - geometry.setMaterial(material); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(20f); - flyCam.setZoomSpeed(20f); - - cam.setLocation(new Vector3f(-3f, 12f, 20f)); - cam.setRotation(new Quaternion(0.01f, 0.97587f, -0.2125f, 0.049f)); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.setAccuracy(0.01f); // 10-msec timestep - physicsSpace.getSolverInfo().setNumIterations(15); - } - - /** - * Put the loaded model into ragdoll mode with buoyancy enabled. - */ - private static void goFloating() { - if (dac.isReady()) { - dac.setRagdollMode(); - } - } - - /** - * Load the BaseMesh model. - */ - private void loadBaseMesh() { - cgModel = (Node) assetManager.loadModel("Models/BaseMesh/BaseMesh.j3o"); - //cgModel.rotate(0f, -1.6f, 0f); - - dac = new BaseMeshControl(); - animationName = "run_01"; - } - - /** - * Load the Elephant model. - */ - private void loadElephant() { - cgModel = (Node) assetManager.loadModel("Models/Elephant/Elephant.j3o"); - cgModel.rotate(0f, 1.6f, 0f); - dac = new ElephantControl(); - animationName = "legUp"; - } - - /** - * Load the Jaime model. - */ - private void loadJaime() { - cgModel = (Node) assetManager.loadModel("Models/Jaime/Jaime-new.j3o"); - Geometry g = (Geometry) cgModel.getChild(0); - RenderState rs = g.getMaterial().getAdditionalRenderState(); - rs.setFaceCullMode(RenderState.FaceCullMode.Off); - - dac = new JaimeControl(); - animationName = "Punches"; - } - - /** - * Load the MhGame model. - */ - private void loadMhGame() { - cgModel = (Node) assetManager.loadModel("Models/MhGame/MhGame.j3o"); - dac = new MhGameControl(); - animationName = "expr-lib-pose"; - } - - /** - * Load the Ninja model. - */ - private void loadNinja() { - cgModel = (Node) assetManager.loadModel("Models/Ninja/Ninja.j3o"); - cgModel.rotate(0f, 3f, 0f); - dac = new NinjaControl(); - animationName = "Walk"; - } - - /** - * Load the Oto model. - */ - private void loadOto() { - cgModel = (Node) assetManager.loadModel("Models/Oto/Oto.j3o"); - dac = new OtoControl(); - animationName = "Walk"; - } - - /** - * Load the Puppet model. - */ - private void loadPuppet() { - cgModel = (Node) assetManager.loadModel("Models/Puppet/Puppet.j3o"); - AnimMigrationUtils.migrate(cgModel); - dac = new PuppetControl(); - animationName = "walk"; - } - - /** - * Load the Sinbad model without attachments. - */ - private void loadSinbad() { - cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); - dac = new SinbadControl(); - animationName = "Dance"; - } - - /** - * Load the Sinbad model with an attached sword. - */ - private void loadSinbadWith1Sword() { - cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); - - Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); - List list = MySpatial.listSpatials(sword); - for (Spatial spatial : list) { - spatial.setShadowMode(RenderQueue.ShadowMode.Cast); - } - - LinkConfig swordConfig = new LinkConfig( - 5f, MassHeuristic.Density, ShapeHeuristic.VertexHull, - Vector3f.UNIT_XYZ, CenterHeuristic.AABB); - dac = new SinbadControl(); - dac.attach("Handle.R", swordConfig, sword); - - animationName = "IdleTop"; - } - - /** - * Load the Sinbad model with 2 attached swords. - */ - private void loadSinbadWithSwords() { - cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); - - Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); - List list = MySpatial.listSpatials(sword); - for (Spatial spatial : list) { - spatial.setShadowMode(RenderQueue.ShadowMode.Cast); - } - - LinkConfig swordConfig = new LinkConfig( - 5f, MassHeuristic.Density, ShapeHeuristic.VertexHull, - Vector3f.UNIT_XYZ, CenterHeuristic.AABB); - dac = new SinbadControl(); - dac.attach("Handle.L", swordConfig, sword); - dac.attach("Handle.R", swordConfig, sword); - - animationName = "RunTop"; - } - - /** - * Toggle mesh rendering on/off. - */ - private static void toggleMeshes() { - Spatial.CullHint hint = cgModel.getLocalCullHint(); - if (hint == Spatial.CullHint.Inherit - || hint == Spatial.CullHint.Never) { - hint = Spatial.CullHint.Always; - } else if (hint == Spatial.CullHint.Always) { - hint = Spatial.CullHint.Never; - } - cgModel.setCullHint(hint); - } - - /** - * Toggle the skeleton visualizer on/off. - */ - private static void toggleSkeleton() { - boolean enabled = sv.isEnabled(); - sv.setEnabled(!enabled); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.Application; +import com.jme3.app.StatsAppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.system.AppSettings; +import com.jme3.util.SkyFactory; +import com.jme3.water.SimpleWaterProcessor; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InfluenceUtil; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.debug.SkeletonVisualizer; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.controllers.BuoyController; +import jme3utilities.minie.test.tunings.BaseMeshControl; +import jme3utilities.minie.test.tunings.ElephantControl; +import jme3utilities.minie.test.tunings.JaimeControl; +import jme3utilities.minie.test.tunings.MhGameControl; +import jme3utilities.minie.test.tunings.NinjaControl; +import jme3utilities.minie.test.tunings.OtoControl; +import jme3utilities.minie.test.tunings.PuppetControl; +import jme3utilities.minie.test.tunings.SinbadControl; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; + +/** + * Demonstrate BuoyController. + *

+ * Seen in the March 2019 demo video: + * https://www.youtube.com/watch?v=eq09m7pbk5A + * + * @author Stephen Gold sgold@sonic.net + */ +public class BuoyDemo extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * Y coordinate of the water's surface (in world coordinates) + */ + final public static float surfaceElevation = 0f; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(BuoyDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = BuoyDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * Control being tested + */ + private static DynamicAnimControl dac; + /** + * root node of the C-G model on which the Control is being tested + */ + private static Node cgModel; + /** + * scene-graph subtree containing all geometries visible in reflections + */ + final private static Node reflectiblesNode = new Node("reflectibles"); + /** + * scene-graph subtree containing all reflective geometries + */ + final private static Node reflectorsNode = new Node("reflectors"); + /** + * scene processor for water effects + */ + private static SimpleWaterProcessor processor; + /** + * visualizer for the skeleton of the C-G model + */ + private static SkeletonVisualizer sv; + /** + * name of the Animation/Action to play on the C-G model + */ + private static String animationName = null; + // ************************************************************************* + // constructors + + /** + * Instantiate the BuoyDemo application. + */ + public BuoyDemo() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the BuoyDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new BuoyDemo(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + super.acorusInit(); + + rootNode.attachChild(reflectiblesNode); + rootNode.attachChild(reflectorsNode); + + configureCamera(); + configureDumper(); + configurePhysics(); + addLighting(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + addSurface(); + addSky(); + addModel("Sinbad"); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + super.configureDumper(); + + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.JointsInSpaces, true); + } + + /** + * Calculate screen bounds for the detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes along the top edge of the viewport. + float margin = 10f; // in pixels + float width = viewPortWidth - 2f * margin; + float height = viewPortHeight - 2f * margin; + float leftX = margin; + float topY = margin + height; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + dim.bind(asDumpScenes, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind("go floating", KeyInput.KEY_0, KeyInput.KEY_SPACE); + + dim.bind("load BaseMesh", KeyInput.KEY_F11); + dim.bind("load Elephant", KeyInput.KEY_F3); + dim.bind("load Jaime", KeyInput.KEY_F2); + dim.bind("load MhGame", KeyInput.KEY_F9); + dim.bind("load Ninja", KeyInput.KEY_F7); + dim.bind("load Oto", KeyInput.KEY_F6); + dim.bind("load Puppet", KeyInput.KEY_F8); + dim.bind("load Sinbad", KeyInput.KEY_F1); + dim.bind("load SinbadWith1Sword", KeyInput.KEY_F10); + dim.bind("load SinbadWithSwords", KeyInput.KEY_F4); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("rotateLeft", KeyInput.KEY_LEFT); + dim.bindSignal("rotateRight", KeyInput.KEY_RIGHT); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleDebug, KeyInput.KEY_SLASH); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind("toggle meshes", KeyInput.KEY_M); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind("toggle skeleton", KeyInput.KEY_V); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "go floating": + goFloating(); + return; + + case "toggle meshes": + toggleMeshes(); + return; + case "toggle skeleton": + toggleSkeleton(); + return; + default: + } + + String[] words = actionString.split(" "); + if (words.length == 2 && "load".equals(words[0])) { + addModel(words[1]); + return; + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + Signals signals = getSignals(); + + float rotateAngle = 0f; + if (signals.test("rotateRight")) { + rotateAngle += tpf; + } + if (signals.test("rotateLeft")) { + rotateAngle -= tpf; + } + if (rotateAngle != 0f) { + rotateAngle /= speed; + Quaternion orientation = MySpatial.worldOrientation(cgModel, null); + Quaternion rotate + = new Quaternion().fromAngles(0f, rotateAngle, 0f); + rotate.mult(orientation, orientation); + MySpatial.setWorldOrientation(cgModel, orientation); + } + } + // ************************************************************************* + // private methods + + /** + * Add lighting and reflections to the main scene. + */ + private void addLighting() { + ColorRGBA ambientColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootNode.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(-1f, -0.2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + sun.setName("sun"); + + processor = new SimpleWaterProcessor(assetManager); + viewPort.addProcessor(processor); + processor.setLightPosition(direction.mult(1000f)); + Plane surface = new Plane(Vector3f.UNIT_Y, surfaceElevation); + processor.setPlane(surface); + processor.setReflectionScene(reflectiblesNode); + + // Clip everything below the surface. + processor.setReflectionClippingOffset(-0.1f); + + // Configure water and wave parameters. + float waveHeight = 0.2f; + processor.setDistortionScale(waveHeight); + float waterTransparency = 0.4f; + processor.setWaterDepth(waterTransparency); + float waveSpeed = 0.06f; + processor.setWaveSpeed(waveSpeed); + } + + /** + * Add an animated model to the scene, removing any previously added model. + * + * @param modelName the name of the model to add (not null, not empty) + */ + private void addModel(String modelName) { + if (cgModel != null) { + dac.getSpatial().removeControl(dac); + reflectiblesNode.detachChild(cgModel); + rootNode.removeControl(sv); + } + + switch (modelName) { + case "BaseMesh": + loadBaseMesh(); + break; + case "Elephant": + loadElephant(); + break; + case "Jaime": + loadJaime(); + break; + case "MhGame": + loadMhGame(); + break; + case "Ninja": + loadNinja(); + break; + case "Oto": + loadOto(); + break; + case "Puppet": + loadPuppet(); + break; + case "Sinbad": + loadSinbad(); + break; + case "SinbadWith1Sword": + loadSinbadWith1Sword(); + break; + case "SinbadWithSwords": + loadSinbadWithSwords(); + break; + default: + throw new IllegalArgumentException(modelName); + } + + List list = MySpatial.listSpatials(cgModel); + for (Spatial spatial : list) { + spatial.setShadowMode(RenderQueue.ShadowMode.Cast); + } + cgModel.setCullHint(Spatial.CullHint.Never); + + reflectiblesNode.attachChild(cgModel); + setCgmHeight(cgModel, 10f); + centerCgm(cgModel); + + SkinningControl sc = (SkinningControl) RagUtils.findSControl(cgModel); + Spatial controlledSpatial = sc.getSpatial(); + + controlledSpatial.addControl(dac); + dac.setGravity(new Vector3f(0f, -50f, 0f)); + PhysicsSpace physicsSpace = getPhysicsSpace(); + dac.setPhysicsSpace(physicsSpace); + + // Add buoyancy to each BoneLink. + List links = dac.listLinks(PhysicsLink.class); + float density = 1.5f; + for (PhysicsLink link : links) { + BuoyController buoy + = new BuoyController(link, density, surfaceElevation); + link.addIKController(buoy); + } + + AnimComposer composer + = controlledSpatial.getControl(AnimComposer.class); + composer.setCurrentAction(animationName); + + sv = new SkeletonVisualizer(assetManager, sc); + sv.setLineColor(ColorRGBA.Yellow); + InfluenceUtil.hideNonInfluencers(sv, sc); + rootNode.addControl(sv); + + if (isPaused()) { + togglePause(); + } + } + + /** + * Add a cube-mapped sky. + */ + private void addSky() { + String assetPath = "Textures/Sky/Bright/BrightSky.dds"; + Spatial sky = SkyFactory.createSky( + assetManager, assetPath, SkyFactory.EnvMapType.CubeMap); + reflectiblesNode.attachChild(sky); + } + + /** + * Add a large Quad to represent the surface of the water. + */ + private static void addSurface() { + float diameter = 2000f; + Mesh mesh = new Quad(diameter, diameter); + mesh.scaleTextureCoordinates(new Vector2f(80f, 80f)); + + Geometry geometry = new Geometry("water surface", mesh); + reflectorsNode.attachChild(geometry); + + geometry.move(-diameter / 2, 0f, diameter / 2); + Quaternion rot = new Quaternion(); + rot.fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X); + geometry.setLocalRotation(rot); + Material material = processor.getMaterial(); + geometry.setMaterial(material); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(20f); + flyCam.setZoomSpeed(20f); + + cam.setLocation(new Vector3f(-3f, 12f, 20f)); + cam.setRotation(new Quaternion(0.01f, 0.97587f, -0.2125f, 0.049f)); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.setAccuracy(0.01f); // 10-msec timestep + physicsSpace.getSolverInfo().setNumIterations(15); + } + + /** + * Put the loaded model into ragdoll mode with buoyancy enabled. + */ + private static void goFloating() { + if (dac.isReady()) { + dac.setRagdollMode(); + } + } + + /** + * Load the BaseMesh model. + */ + private void loadBaseMesh() { + cgModel = (Node) assetManager.loadModel("Models/BaseMesh/BaseMesh.j3o"); + //cgModel.rotate(0f, -1.6f, 0f); + + dac = new BaseMeshControl(); + animationName = "run_01"; + } + + /** + * Load the Elephant model. + */ + private void loadElephant() { + cgModel = (Node) assetManager.loadModel("Models/Elephant/Elephant.j3o"); + cgModel.rotate(0f, 1.6f, 0f); + dac = new ElephantControl(); + animationName = "legUp"; + } + + /** + * Load the Jaime model. + */ + private void loadJaime() { + cgModel = (Node) assetManager.loadModel("Models/Jaime/Jaime-new.j3o"); + Geometry g = (Geometry) cgModel.getChild(0); + RenderState rs = g.getMaterial().getAdditionalRenderState(); + rs.setFaceCullMode(RenderState.FaceCullMode.Off); + + dac = new JaimeControl(); + animationName = "Punches"; + } + + /** + * Load the MhGame model. + */ + private void loadMhGame() { + cgModel = (Node) assetManager.loadModel("Models/MhGame/MhGame.j3o"); + dac = new MhGameControl(); + animationName = "expr-lib-pose"; + } + + /** + * Load the Ninja model. + */ + private void loadNinja() { + cgModel = (Node) assetManager.loadModel("Models/Ninja/Ninja.j3o"); + cgModel.rotate(0f, 3f, 0f); + dac = new NinjaControl(); + animationName = "Walk"; + } + + /** + * Load the Oto model. + */ + private void loadOto() { + cgModel = (Node) assetManager.loadModel("Models/Oto/Oto.j3o"); + dac = new OtoControl(); + animationName = "Walk"; + } + + /** + * Load the Puppet model. + */ + private void loadPuppet() { + cgModel = (Node) assetManager.loadModel("Models/Puppet/Puppet.j3o"); + AnimMigrationUtils.migrate(cgModel); + dac = new PuppetControl(); + animationName = "walk"; + } + + /** + * Load the Sinbad model without attachments. + */ + private void loadSinbad() { + cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); + dac = new SinbadControl(); + animationName = "Dance"; + } + + /** + * Load the Sinbad model with an attached sword. + */ + private void loadSinbadWith1Sword() { + cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); + + Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); + List list = MySpatial.listSpatials(sword); + for (Spatial spatial : list) { + spatial.setShadowMode(RenderQueue.ShadowMode.Cast); + } + + LinkConfig swordConfig = new LinkConfig( + 5f, MassHeuristic.Density, ShapeHeuristic.VertexHull, + Vector3f.UNIT_XYZ, CenterHeuristic.AABB); + dac = new SinbadControl(); + dac.attach("Handle.R", swordConfig, sword); + + animationName = "IdleTop"; + } + + /** + * Load the Sinbad model with 2 attached swords. + */ + private void loadSinbadWithSwords() { + cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); + + Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); + List list = MySpatial.listSpatials(sword); + for (Spatial spatial : list) { + spatial.setShadowMode(RenderQueue.ShadowMode.Cast); + } + + LinkConfig swordConfig = new LinkConfig( + 5f, MassHeuristic.Density, ShapeHeuristic.VertexHull, + Vector3f.UNIT_XYZ, CenterHeuristic.AABB); + dac = new SinbadControl(); + dac.attach("Handle.L", swordConfig, sword); + dac.attach("Handle.R", swordConfig, sword); + + animationName = "RunTop"; + } + + /** + * Toggle mesh rendering on/off. + */ + private static void toggleMeshes() { + Spatial.CullHint hint = cgModel.getLocalCullHint(); + if (hint == Spatial.CullHint.Inherit + || hint == Spatial.CullHint.Never) { + hint = Spatial.CullHint.Always; + } else if (hint == Spatial.CullHint.Always) { + hint = Spatial.CullHint.Never; + } + cgModel.setCullHint(hint); + } + + /** + * Toggle the skeleton visualizer on/off. + */ + private static void toggleSkeleton() { + boolean enabled = sv.isEnabled(); + sv.setEnabled(!enabled); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/Drop.java b/MinieExamples/src/main/java/jme3utilities/minie/test/Drop.java index 383e81a70..b0b825adf 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/Drop.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/Drop.java @@ -1,892 +1,892 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.asset.AssetManager; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.TorsoLink; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsGhostObject; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.ConfigFlag; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.control.AbstractControl; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.util.Collection; -import java.util.List; -import java.util.Random; -import java.util.TreeSet; -import java.util.logging.Logger; -import jme3utilities.MeshNormals; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.noise.Generator; -import jme3utilities.mesh.ClothGrid; -import jme3utilities.mesh.Icosphere; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.shape.CompoundTestShapes; -import jme3utilities.minie.test.shape.ShapeGenerator; -import jme3utilities.minie.test.tunings.BaseMeshControl; - -/** - * A group of dynamic bodies and physics joints in the DropTest application. - * - * @author Stephen Gold sgold@sonic.net - */ -class Drop implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(Drop.class.getName()); - // ************************************************************************* - // fields - - /** - * application that contains this Drop - */ - final private PhysicsDemo appInstance; - /** - * physics bodies in this Drop, all must be dynamic - */ - final private Collection allBodies = new TreeSet<>(); - /** - * all physics joints in this Drop - */ - final private Collection allJoints = new TreeSet<>(); - /** - * included ragdoll, or null if none - */ - private DynamicAnimControl dac = null; - /** - * configured total mass of all bodies in this Drop - */ - final private float totalMass; - /** - * configured type of Drop - */ - final private String typeName; - /** - * configured initial local-to-PhysicsSpace Transform - */ - final private Transform startPosition; - /** - * local inverse inertia vector for next rigid body (or null) - */ - private Vector3f inverseInertia = null; - // ************************************************************************* - // constructors - - /** - * Instantiate a Drop of the specified type, with the specified mass, - * initial position, and application data. - * - * @param appInstance (not null, alias created) - * @param typeName (not null) - * @param totalMass (>0) - * @param startPosition the local-to-PhysicsSpace Transform (not null, - * unaffected) - */ - Drop(PhysicsDemo appInstance, String typeName, float totalMass, - Transform startPosition) { - Validate.nonNull(appInstance, "application instance"); - Validate.nonNull(typeName, "type name"); - Validate.positive(totalMass, "total mass"); - Validate.nonNull(startPosition, "start position"); - - this.appInstance = appInstance; - this.typeName = typeName; - this.totalMass = totalMass; - this.startPosition = startPosition.clone(); - - create(); - } - // ************************************************************************* - // new methods exposed - - /** - * Add all controls, bodies, and joints to the application's PhysicsSpace. - */ - void addToSpace() { - if (hasDac()) { - PhysicsSpace space = appInstance.getPhysicsSpace(); - dac.setPhysicsSpace(space); - for (PhysicsBody body : allBodies) { - appInstance.postAdd(body); - } - } - - for (PhysicsBody body : allBodies) { - if (!body.isInWorld()) { - appInstance.addCollisionObject(body); - } - } - - for (PhysicsJoint joint : allJoints) { - if (joint.getPhysicsSpace() == null) { - appInstance.addJoint(joint); - } - } - } - - /** - * Test whether this Drop includes a DynamicAnimControl. - * - * @return true if a DynamicAnimControl is included, otherwise false - */ - boolean hasDac() { - if (dac == null) { - return false; - } else { - return true; - } - } - - /** - * Test for contacts with this drop's convex hull. - * - * @return true if there are contacts, otherwise false - */ - boolean hasHullContacts() { - int numBodies = allBodies.size(); - FloatBuffer[] vertices = new FloatBuffer[numBodies]; - int bodyIndex = 0; - int totalFloats = 0; - for (PhysicsBody body : allBodies) { - int numFloats; - if (body instanceof PhysicsRigidBody) { - CollisionShape shape = body.getCollisionShape(); - vertices[bodyIndex] = DebugShapeFactory.debugVertices( - shape, DebugShapeFactory.lowResolution); - numFloats = vertices[bodyIndex].capacity(); - - // Transform scaled shape coordinates to physics-space. - Quaternion orientation = body.getPhysicsRotation(null); - MyBuffer.rotate(vertices[bodyIndex], 0, numFloats, orientation); - Vector3f location = body.getPhysicsLocation(null); - MyBuffer.translate(vertices[bodyIndex], 0, numFloats, location); - - } else { - PhysicsSoftBody softBody = (PhysicsSoftBody) body; - int numNodes = softBody.countNodes(); - numFloats = MyVector3f.numAxes * numNodes; - vertices[bodyIndex] = BufferUtils.createFloatBuffer(numFloats); - softBody.copyLocations(vertices[bodyIndex]); - } - - vertices[bodyIndex].limit(numFloats); - totalFloats += numFloats; - ++bodyIndex; - } - - HullCollisionShape hullShape; - if (numBodies == 1) { - hullShape = new HullCollisionShape(vertices[0]); - - } else { - FloatBuffer all = BufferUtils.createFloatBuffer(totalFloats); - for (FloatBuffer buffer : vertices) { - buffer.rewind(); - all.put(buffer); - } - all.limit(totalFloats); - hullShape = new HullCollisionShape(all); - } - - PhysicsGhostObject ghost = new PhysicsGhostObject(hullShape); - PhysicsSpace space = appInstance.getPhysicsSpace(); - int numContacts = space.contactTest(ghost, null); - boolean result = (numContacts > 0); - //space.addCollisionObject(ghost); - - return result; - } - - /** - * Apply a vertical impulse to each rigid body. - * - * @param deltaV the desired change in Y velocity - */ - void pop(float deltaV) { - for (PhysicsBody body : allBodies) { - if (body instanceof PhysicsRigidBody) { - float impulse = body.getMass() * deltaV; - Vector3f impulseVector = new Vector3f(0f, impulse, 0f); - Generator random = appInstance.getGenerator(); - Vector3f offset = random.nextVector3f().multLocal(0.2f); - ((PhysicsRigidBody) body).applyImpulse(impulseVector, offset); - } else { - PhysicsSoftBody softBody = (PhysicsSoftBody) body; - Vector3f velocityVector = new Vector3f(0f, deltaV, 0f); - softBody.addVelocity(velocityVector); - } - } - } - - /** - * Remove all controls, joints, and bodies from the application's - * PhysicsSpace. - */ - void removeFromSpace() { - if (hasDac()) { - dac.setPhysicsSpace(null); - } - - PhysicsSpace space = appInstance.getPhysicsSpace(); - for (PhysicsJoint joint : allJoints) { - if (joint.getPhysicsSpace() == space) { - space.removeJoint(joint); - } - } - - for (PhysicsBody body : allBodies) { - if (body.isInWorld()) { - space.removeCollisionObject(body); - } - } - } - - /** - * Callback invoked once per frame. - */ - void update() { - // As soon as the DAC is ready, put all physics links into dynamic mode. - if (hasDac() && dac.isReady()) { - TorsoLink torso = dac.getTorsoLink(); - if (torso.isKinematic()) { - PhysicsSpace space = appInstance.getPhysicsSpace(); - Vector3f gravity = space.getGravity(null); - dac.setDynamicSubtree(torso, gravity, false); - } - } - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object is included in this Drop. - * - * @param obj the joint or collision object to test (unaffected) - * @return return true if included, false if not - */ - @Override - public boolean displayObject(Object obj) { - boolean result; - if (obj instanceof PhysicsBody) { - result = allBodies.contains(obj); - } else if (obj instanceof PhysicsJoint) { - result = allJoints.contains(obj); - } else { - result = false; - } - - return result; - } - // ************************************************************************* - // private methods - - /** - * Create bodies and joints based on the configuration. - */ - private void create() { - ShapeGenerator random = appInstance.getGenerator(); - - CollisionShape shape; - switch (typeName) { - case "ankh": - case "duck": - case "heart": - case "horseshoe": - case "sword": - case "table": - case "teapot": - case "teapotGi": - case "thumbTack": - shape = appInstance.findShape(typeName); - createRigidBody( - shape, totalMass, MeshNormals.Facet, startPosition); - break; - - case "banana": - case "barbell": - case "barrel": - case "bowlingPin": - case "knucklebone": - case "ladder": - case "link": - case "top": - shape = appInstance.findShape(typeName); - createRigidBody( - shape, totalMass, MeshNormals.Smooth, startPosition); - break; - - case "box": - case "frame": - case "halfPipe": - case "hull": - case "iBeam": - case "lidlessBox": - case "platonic": - case "prism": - case "pyramid": - case "star": - case "tetrahedron": - case "triangularFrame": - case "trident": - case "washer": - shape = random.nextShape(typeName); - createRigidBody( - shape, totalMass, MeshNormals.Facet, startPosition); - break; - - case "capsule": - case "cone": - case "coneBox": - case "cylinder": - case "cylinderBox": - case "dome": - case "football": - case "multiSphere": - case "roundedDisc": - case "saucer": - case "snowman": - case "torus": - shape = random.nextShape(typeName); - createRigidBody( - shape, totalMass, MeshNormals.Smooth, startPosition); - break; - - case "bowl": - shape = appInstance.findShape("bowl"); - this.inverseInertia - = CompoundTestShapes.bowlInverseInertia.divide( - totalMass); - createRigidBody( - shape, totalMass, MeshNormals.Smooth, startPosition); - break; - - case "chair": - shape = appInstance.findShape("chair"); - this.inverseInertia - = CompoundTestShapes.chairInverseInertia.divide( - totalMass); - createRigidBody( - shape, totalMass, MeshNormals.Facet, startPosition); - break; - - case "digit": - shape = randomDigit(); - createRigidBody( - shape, totalMass, MeshNormals.Facet, startPosition); - break; - - case "breakableRod": - case "chain": - case "diptych": - case "flail": - case "ragdoll": - createJointed(); - break; - - case "letter": - shape = randomLetter(); - createRigidBody( - shape, totalMass, MeshNormals.Facet, startPosition); - break; - - case "madMallet": - shape = randomMallet(false); - createRigidBody( - shape, totalMass, MeshNormals.Smooth, startPosition); - break; - - case "mallet": - shape = randomMallet(true); - createRigidBody( - shape, totalMass, MeshNormals.Smooth, startPosition); - break; - - case "sphere": - shape = random.nextShape(typeName); - createRigidBody( - shape, totalMass, MeshNormals.Sphere, startPosition); - break; - - case "cloth": - case "squishyBall": - createSoftBody(); - break; - - default: - String message = "typeName = " + MyString.quote(typeName); - throw new IllegalArgumentException(message); - } - } - - /** - * Create a breakable rod. Its length runs along the drop's local Y axis. - * - * @param totalLength the total length (in physics-space units, >0) - * @param radius the radius (in physics-space units, >0) - */ - private void createBreakableRod(float totalLength, float radius) { - float pieceLength = totalLength / 2f; - float pieceMass = totalMass / 2f; - - CylinderCollisionShape pieceShape = new CylinderCollisionShape( - radius, pieceLength, PhysicsSpace.AXIS_Y); - - Transform tmpPosition = new Transform(); - tmpPosition.getTranslation().y = pieceLength / 2; - Vector3f pivotInA = tmpPosition.getTranslation().negate(); - MyMath.combine(tmpPosition, startPosition, tmpPosition); - PhysicsRigidBody a = createRigidBody( - pieceShape, pieceMass, MeshNormals.Smooth, tmpPosition); - - tmpPosition.loadIdentity(); - tmpPosition.getTranslation().y = -pieceLength / 2; - Vector3f pivotInB = tmpPosition.getTranslation().negate(); - MyMath.combine(tmpPosition, startPosition, tmpPosition); - PhysicsRigidBody b = createRigidBody( - pieceShape, pieceMass, MeshNormals.Smooth, tmpPosition); - - Matrix3f rotInA = Matrix3f.IDENTITY; - Matrix3f rotInB = Matrix3f.IDENTITY; - New6Dof fixed = new New6Dof( - a, b, pivotInA, pivotInB, rotInA, rotInB, RotationOrder.YZX); - allJoints.add(fixed); - - fixed.setBreakingImpulseThreshold(4f); - - for (int i = PhysicsSpace.AXIS_X; i <= PhysicsSpace.AXIS_Z; ++i) { - RotationMotor motor = fixed.getRotationMotor(i); - motor.set(MotorParam.LowerLimit, 0f); - motor.set(MotorParam.UpperLimit, 0f); - } - } - - /** - * Create a chain of links. Initially the chain is laid out along the drop's - * local Y axis. - * - * @param numLinks the number of links (>0) - * @param thickness the thickness of each link (in physics-space units, - * >0) - */ - private void createChain(int numLinks, float thickness) { - float halfThickness = thickness / 2f; - float linkIhh = 3f * halfThickness; - float linkIhw = 1.1f * halfThickness; - float angleStep = FastMath.HALF_PI; - float yStep = 1.9f * linkIhh; // center-to-center - float linkMass = totalMass / numLinks; - - CompoundCollisionShape shape - = CompoundTestShapes.makeLink(linkIhh, linkIhw, halfThickness); - - Quaternion rotationStep - = new Quaternion().fromAngles(0f, angleStep, 0f); - - float y0 = -yStep * (numLinks - 1) / 2f; - Vector3f startOffset = new Vector3f(0f, y0, 0f); - Transform transform = new Transform(startOffset); - Vector3f linkLocation = transform.getTranslation(); // alias - Quaternion linkOrientation = transform.getRotation(); // alias - - Transform tmpPosition = new Transform(); - for (int linkIndex = 0; linkIndex < numLinks; ++linkIndex) { - tmpPosition.set(transform); - MyMath.combine(tmpPosition, startPosition, tmpPosition); - createRigidBody( - shape, linkMass, MeshNormals.Smooth, tmpPosition); - - linkLocation.y += yStep; - rotationStep.mult(linkOrientation, linkOrientation); - } - } - - /** - * Create a diptych consisting of 2 thin boxes, joined by a hinge. The axis - * of the hinge is the drop's local Y axis. - * - * @param height the desired height of each box (>0) - * @param thickness the desired thickness of each box (>0) - * @param boxWidth the desired width of each box (>0) - */ - private void createDiptych(float height, float thickness, float boxWidth) { - float boxHalfWidth = boxWidth / 2f; - float halfThickness = thickness / 2f; - CollisionShape boxShape = new BoxCollisionShape( - boxHalfWidth, height / 2f, halfThickness); - - float boxMass = totalMass / 2f; - - Vector3f pivotInA = new Vector3f(boxHalfWidth, 0f, halfThickness); - Transform aPosition = new Transform(); - aPosition.setTranslation(pivotInA.negate()); - MyMath.combine(aPosition, startPosition, aPosition); - - PhysicsRigidBody a = createRigidBody( - boxShape, boxMass, MeshNormals.Facet, aPosition); - - Vector3f pivotInB = new Vector3f(-boxHalfWidth, 0f, halfThickness); - Transform bPosition = new Transform(); - bPosition.setTranslation(pivotInB.negate()); - MyMath.combine(bPosition, startPosition, bPosition); - - PhysicsRigidBody b = createRigidBody( - boxShape, boxMass, MeshNormals.Facet, bPosition); - - Matrix3f rotInA = Matrix3f.IDENTITY; - Matrix3f rotInB = Matrix3f.IDENTITY; - New6Dof hinge = new New6Dof( - a, b, pivotInA, pivotInB, rotInA, rotInB, RotationOrder.YZX); - allJoints.add(hinge); - - RotationMotor xMotor = hinge.getRotationMotor(PhysicsSpace.AXIS_X); - xMotor.set(MotorParam.LowerLimit, 0f); - xMotor.set(MotorParam.UpperLimit, 0f); - - RotationMotor yMotor = hinge.getRotationMotor(PhysicsSpace.AXIS_Y); - yMotor.set(MotorParam.LowerLimit, 0.1f); - yMotor.set(MotorParam.Equilibrium, 1.6f); - yMotor.set(MotorParam.UpperLimit, FastMath.PI); - yMotor.setSpringEnabled(true); - yMotor.set(MotorParam.Stiffness, 0.01f); // a very weak spring - - RotationMotor zMotor = hinge.getRotationMotor(PhysicsSpace.AXIS_Z); - zMotor.set(MotorParam.LowerLimit, 0f); - zMotor.set(MotorParam.UpperLimit, 0f); - } - - /** - * Create a flail consisting of 2 unequal staves, joined by a pivot. - * Initially both staves lie on the drop's local Y axis. - * - * @param aLength the length of the "A" staff (>0) - * @param bLength the length of the "B" staff (>0) - * @param radius the radius of each staff (>0) - */ - private void createFlail(float aLength, float bLength, float radius) { - float linearDensity = totalMass / (aLength + bLength); - - CollisionShape aShape = new CapsuleCollisionShape( - radius, aLength, PhysicsSpace.AXIS_Y); - float aMass = aLength * linearDensity; - - Vector3f pivotInA = new Vector3f(0f, 3f * radius + aLength / 2f, 0f); - Transform aPosition = new Transform(); - aPosition.setTranslation(pivotInA.negate()); - MyMath.combine(aPosition, startPosition, aPosition); - - PhysicsRigidBody a - = createRigidBody(aShape, aMass, MeshNormals.Smooth, aPosition); - - CollisionShape bShape = new CapsuleCollisionShape( - radius, bLength, PhysicsSpace.AXIS_Y); - float bMass = bLength * linearDensity; - - Vector3f pivotInB = new Vector3f(0f, -3f * radius - bLength / 2f, 0f); - Transform bPosition = new Transform(); - bPosition.setTranslation(pivotInB.negate()); - MyMath.combine(bPosition, startPosition, bPosition); - - PhysicsRigidBody b - = createRigidBody(bShape, bMass, MeshNormals.Smooth, bPosition); - - Matrix3f rotInA = Matrix3f.IDENTITY; - Matrix3f rotInB = Matrix3f.IDENTITY; - New6Dof pivot = new New6Dof( - a, b, pivotInA, pivotInB, rotInA, rotInB, RotationOrder.XYZ); - allJoints.add(pivot); - } - - /** - * Create a jointed Drop based on the configuration. - */ - private void createJointed() { - switch (typeName) { - case "breakableRod": { - float totalLength = 5f; - float radius = 1f; - createBreakableRod(totalLength, radius); - break; - } - - case "chain": { - int numLinks = 10; - float thickness = 0.4f; - createChain(numLinks, thickness); - break; - } - - case "diptych": { - float height = 4f; - float thickness = 0.4f; - float boxWidth = 2f; - createDiptych(height, thickness, boxWidth); - break; - } - - case "flail": { - float aLength = 5f; - float bLength = 2.5f; - float radius = 0.2f; - createFlail(aLength, bLength, radius); - break; - } - - case "ragdoll": { - float scale = 5f; - createRagdoll("Models/BaseMesh/BaseMesh.j3o", scale, - new BaseMeshControl()); - break; - } - - default: - String message = "typeName = " + MyString.quote(typeName); - throw new IllegalArgumentException(message); - } - } - - /** - * Create a ragdoll using DynamicAnimControl. - * - * @param assetPath asset path to the model J3O (not null, not empty) - * @param scale the uniform scale factor to apply (>0) - * @param control a configured DynamicAnimControl to use (not null) - */ - private void createRagdoll( - String assetPath, float scale, DynamicAnimControl control) { - assert scale > 0f : scale; - - AssetManager assetManager = appInstance.getAssetManager(); - Node cgModel = (Node) assetManager.loadModel(assetPath); - cgModel.setLocalTransform(startPosition); - cgModel.setLocalScale(scale); - - this.dac = control; - - AbstractControl ac = RagUtils.findSControl(cgModel); - ac.getSpatial().addControl(control); - - PhysicsRigidBody[] bodies = dac.listRigidBodies(); - for (PhysicsRigidBody body : bodies) { - allBodies.add(body); - body.setDebugMeshNormals(MeshNormals.Smooth); - } - - List boneLinks = dac.listLinks(BoneLink.class); - for (BoneLink link : boneLinks) { - PhysicsJoint joint = link.getJoint(); - allJoints.add(joint); - } - } - - /** - * Create a rigid body at the specified position and add it to this Drop. - * - * @param shape (not null) - * @param mass (>0) - * @param debugMeshNormals (not null) - * @param position (not null, unaffected) - * @return the new body (not null, not in any space) - */ - private PhysicsRigidBody createRigidBody(CollisionShape shape, float mass, - MeshNormals debugMeshNormals, Transform position) { - assert shape != null : typeName; - assert mass > 0f : mass; - assert debugMeshNormals != null : typeName; - - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - allBodies.add(result); - - result.setDebugMeshNormals(debugMeshNormals); - if (inverseInertia != null) { - result.setInverseInertiaLocal(inverseInertia); - } - result.setPhysicsLocation(position.getTranslation()); - result.setPhysicsRotation(position.getRotation()); - - return result; - } - - /** - * Create a soft Drop based on the configuration. - */ - private void createSoftBody() { - PhysicsSoftBody softBody; - switch (typeName) { - case "cloth": { - int numLines = 41; - float lineSpacing = 0.3f; // mesh units - Mesh mesh = new ClothGrid(numLines, numLines, lineSpacing); - - softBody = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); - softBody.applyTranslation(startPosition.getTranslation()); - - softBody.setDebugMeshNormals(MeshNormals.Smooth); - softBody.setMargin(lineSpacing); - - SoftBodyConfig config = softBody.getSoftConfig(); - config.setPositionIterations(20); // default = 1 - break; - } - - case "squishyBall": { - int numRefinementIterations = 3; - float radius = 3f; - Mesh mesh = new Icosphere(numRefinementIterations, radius); - - softBody = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); - softBody.applyTransform(startPosition); - - softBody.setDebugMeshNormals(MeshNormals.Smooth); - - boolean setVolumePose = false; - boolean setFramePose = true; - softBody.setPose(setVolumePose, setFramePose); - - SoftBodyConfig config = softBody.getSoftConfig(); - config.set(Sbcp.PoseMatching, 0.1f); - config.setCollisionFlags(ConfigFlag.SDF_RS, ConfigFlag.VF_SS); - config.setPositionIterations(9); - break; - } - - default: - String message = "typeName = " + MyString.quote(typeName); - throw new IllegalArgumentException(message); - } - - softBody.setMass(totalMass); - SoftBodyConfig config = softBody.getSoftConfig(); - config.setCollisionFlags(ConfigFlag.SDF_RS, ConfigFlag.VF_SS); - config.setPositionIterations(3); - - allBodies.add(softBody); - } - - /** - * Pseudo-randomly select the shape of a decimal digit. - * - * @return the pre-existing instance (not null) - */ - private CollisionShape randomDigit() { - Random random = appInstance.getGenerator(); - char glyphChar = (char) ('0' + random.nextInt(10)); - String glyphString = Character.toString(glyphChar); - CollisionShape result = appInstance.findShape(glyphString); - assert result != null : glyphChar; - - return result; - } - - /** - * Pseudo-randomly select the shape of an uppercase letter. - * - * @return the pre-existing instance (not null) - */ - private CollisionShape randomLetter() { - Random random = appInstance.getGenerator(); - char glyphChar = (char) ('A' + random.nextInt(26)); - String glyphString = Character.toString(glyphChar); - CollisionShape result = appInstance.findShape(glyphString); - assert result != null : glyphChar; - - return result; - } - - /** - * Pseudo-randomly generate an asymmetrical compound shape consisting of 2 - * cylinders, a head and a handle. - * - * @param correctAxes if true, correct the shape's center of mass and - * principal axes - * @return a new instance (not null) - */ - private CollisionShape randomMallet(boolean correctAxes) { - Generator random = appInstance.getGenerator(); - float handleR = 0.5f; - float headR = handleR + random.nextFloat(); - float headHalfLength = headR + random.nextFloat(); - float handleHalfLength = headHalfLength + random.nextFloat(0f, 2.5f); - CompoundCollisionShape result = CompoundTestShapes.makeMadMallet( - handleR, headR, handleHalfLength, headHalfLength); - /* - * At this point, the shape's center of mass lies at the bare end - * of the handle: a "mad" mallet that prefers to stand upright. - */ - if (correctAxes) { - float handleMass = 0.15f; - float headMass = 1f - handleMass; // Put 85% of mass in the head. - FloatBuffer masses - = BufferUtils.createFloatBuffer(handleMass, headMass); - - Vector3f inertia = new Vector3f(); - Transform transform = result.principalAxes(masses, null, inertia); - this.inverseInertia = Vector3f.UNIT_XYZ.divide(inertia); - result.correctAxes(transform); - } - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.asset.AssetManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.TorsoLink; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.ConfigFlag; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.control.AbstractControl; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.TreeSet; +import java.util.logging.Logger; +import jme3utilities.MeshNormals; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.noise.Generator; +import jme3utilities.mesh.ClothGrid; +import jme3utilities.mesh.Icosphere; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.shape.CompoundTestShapes; +import jme3utilities.minie.test.shape.ShapeGenerator; +import jme3utilities.minie.test.tunings.BaseMeshControl; + +/** + * A group of dynamic bodies and physics joints in the DropTest application. + * + * @author Stephen Gold sgold@sonic.net + */ +class Drop implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(Drop.class.getName()); + // ************************************************************************* + // fields + + /** + * application that contains this Drop + */ + final private PhysicsDemo appInstance; + /** + * physics bodies in this Drop, all must be dynamic + */ + final private Collection allBodies = new TreeSet<>(); + /** + * all physics joints in this Drop + */ + final private Collection allJoints = new TreeSet<>(); + /** + * included ragdoll, or null if none + */ + private DynamicAnimControl dac = null; + /** + * configured total mass of all bodies in this Drop + */ + final private float totalMass; + /** + * configured type of Drop + */ + final private String typeName; + /** + * configured initial local-to-PhysicsSpace Transform + */ + final private Transform startPosition; + /** + * local inverse inertia vector for next rigid body (or null) + */ + private Vector3f inverseInertia = null; + // ************************************************************************* + // constructors + + /** + * Instantiate a Drop of the specified type, with the specified mass, + * initial position, and application data. + * + * @param appInstance (not null, alias created) + * @param typeName (not null) + * @param totalMass (>0) + * @param startPosition the local-to-PhysicsSpace Transform (not null, + * unaffected) + */ + Drop(PhysicsDemo appInstance, String typeName, float totalMass, + Transform startPosition) { + Validate.nonNull(appInstance, "application instance"); + Validate.nonNull(typeName, "type name"); + Validate.positive(totalMass, "total mass"); + Validate.nonNull(startPosition, "start position"); + + this.appInstance = appInstance; + this.typeName = typeName; + this.totalMass = totalMass; + this.startPosition = startPosition.clone(); + + create(); + } + // ************************************************************************* + // new methods exposed + + /** + * Add all controls, bodies, and joints to the application's PhysicsSpace. + */ + void addToSpace() { + if (hasDac()) { + PhysicsSpace space = appInstance.getPhysicsSpace(); + dac.setPhysicsSpace(space); + for (PhysicsBody body : allBodies) { + appInstance.postAdd(body); + } + } + + for (PhysicsBody body : allBodies) { + if (!body.isInWorld()) { + appInstance.addCollisionObject(body); + } + } + + for (PhysicsJoint joint : allJoints) { + if (joint.getPhysicsSpace() == null) { + appInstance.addJoint(joint); + } + } + } + + /** + * Test whether this Drop includes a DynamicAnimControl. + * + * @return true if a DynamicAnimControl is included, otherwise false + */ + boolean hasDac() { + if (dac == null) { + return false; + } else { + return true; + } + } + + /** + * Test for contacts with this drop's convex hull. + * + * @return true if there are contacts, otherwise false + */ + boolean hasHullContacts() { + int numBodies = allBodies.size(); + FloatBuffer[] vertices = new FloatBuffer[numBodies]; + int bodyIndex = 0; + int totalFloats = 0; + for (PhysicsBody body : allBodies) { + int numFloats; + if (body instanceof PhysicsRigidBody) { + CollisionShape shape = body.getCollisionShape(); + vertices[bodyIndex] = DebugShapeFactory.debugVertices( + shape, DebugShapeFactory.lowResolution); + numFloats = vertices[bodyIndex].capacity(); + + // Transform scaled shape coordinates to physics-space. + Quaternion orientation = body.getPhysicsRotation(null); + MyBuffer.rotate(vertices[bodyIndex], 0, numFloats, orientation); + Vector3f location = body.getPhysicsLocation(null); + MyBuffer.translate(vertices[bodyIndex], 0, numFloats, location); + + } else { + PhysicsSoftBody softBody = (PhysicsSoftBody) body; + int numNodes = softBody.countNodes(); + numFloats = MyVector3f.numAxes * numNodes; + vertices[bodyIndex] = BufferUtils.createFloatBuffer(numFloats); + softBody.copyLocations(vertices[bodyIndex]); + } + + vertices[bodyIndex].limit(numFloats); + totalFloats += numFloats; + ++bodyIndex; + } + + HullCollisionShape hullShape; + if (numBodies == 1) { + hullShape = new HullCollisionShape(vertices[0]); + + } else { + FloatBuffer all = BufferUtils.createFloatBuffer(totalFloats); + for (FloatBuffer buffer : vertices) { + buffer.rewind(); + all.put(buffer); + } + all.limit(totalFloats); + hullShape = new HullCollisionShape(all); + } + + PhysicsGhostObject ghost = new PhysicsGhostObject(hullShape); + PhysicsSpace space = appInstance.getPhysicsSpace(); + int numContacts = space.contactTest(ghost, null); + boolean result = (numContacts > 0); + //space.addCollisionObject(ghost); + + return result; + } + + /** + * Apply a vertical impulse to each rigid body. + * + * @param deltaV the desired change in Y velocity + */ + void pop(float deltaV) { + for (PhysicsBody body : allBodies) { + if (body instanceof PhysicsRigidBody) { + float impulse = body.getMass() * deltaV; + Vector3f impulseVector = new Vector3f(0f, impulse, 0f); + Generator random = appInstance.getGenerator(); + Vector3f offset = random.nextVector3f().multLocal(0.2f); + ((PhysicsRigidBody) body).applyImpulse(impulseVector, offset); + } else { + PhysicsSoftBody softBody = (PhysicsSoftBody) body; + Vector3f velocityVector = new Vector3f(0f, deltaV, 0f); + softBody.addVelocity(velocityVector); + } + } + } + + /** + * Remove all controls, joints, and bodies from the application's + * PhysicsSpace. + */ + void removeFromSpace() { + if (hasDac()) { + dac.setPhysicsSpace(null); + } + + PhysicsSpace space = appInstance.getPhysicsSpace(); + for (PhysicsJoint joint : allJoints) { + if (joint.getPhysicsSpace() == space) { + space.removeJoint(joint); + } + } + + for (PhysicsBody body : allBodies) { + if (body.isInWorld()) { + space.removeCollisionObject(body); + } + } + } + + /** + * Callback invoked once per frame. + */ + void update() { + // As soon as the DAC is ready, put all physics links into dynamic mode. + if (hasDac() && dac.isReady()) { + TorsoLink torso = dac.getTorsoLink(); + if (torso.isKinematic()) { + PhysicsSpace space = appInstance.getPhysicsSpace(); + Vector3f gravity = space.getGravity(null); + dac.setDynamicSubtree(torso, gravity, false); + } + } + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object is included in this Drop. + * + * @param obj the joint or collision object to test (unaffected) + * @return return true if included, false if not + */ + @Override + public boolean displayObject(Object obj) { + boolean result; + if (obj instanceof PhysicsBody) { + result = allBodies.contains(obj); + } else if (obj instanceof PhysicsJoint) { + result = allJoints.contains(obj); + } else { + result = false; + } + + return result; + } + // ************************************************************************* + // private methods + + /** + * Create bodies and joints based on the configuration. + */ + private void create() { + ShapeGenerator random = appInstance.getGenerator(); + + CollisionShape shape; + switch (typeName) { + case "ankh": + case "duck": + case "heart": + case "horseshoe": + case "sword": + case "table": + case "teapot": + case "teapotGi": + case "thumbTack": + shape = appInstance.findShape(typeName); + createRigidBody( + shape, totalMass, MeshNormals.Facet, startPosition); + break; + + case "banana": + case "barbell": + case "barrel": + case "bowlingPin": + case "knucklebone": + case "ladder": + case "link": + case "top": + shape = appInstance.findShape(typeName); + createRigidBody( + shape, totalMass, MeshNormals.Smooth, startPosition); + break; + + case "box": + case "frame": + case "halfPipe": + case "hull": + case "iBeam": + case "lidlessBox": + case "platonic": + case "prism": + case "pyramid": + case "star": + case "tetrahedron": + case "triangularFrame": + case "trident": + case "washer": + shape = random.nextShape(typeName); + createRigidBody( + shape, totalMass, MeshNormals.Facet, startPosition); + break; + + case "capsule": + case "cone": + case "coneBox": + case "cylinder": + case "cylinderBox": + case "dome": + case "football": + case "multiSphere": + case "roundedDisc": + case "saucer": + case "snowman": + case "torus": + shape = random.nextShape(typeName); + createRigidBody( + shape, totalMass, MeshNormals.Smooth, startPosition); + break; + + case "bowl": + shape = appInstance.findShape("bowl"); + this.inverseInertia + = CompoundTestShapes.bowlInverseInertia.divide( + totalMass); + createRigidBody( + shape, totalMass, MeshNormals.Smooth, startPosition); + break; + + case "chair": + shape = appInstance.findShape("chair"); + this.inverseInertia + = CompoundTestShapes.chairInverseInertia.divide( + totalMass); + createRigidBody( + shape, totalMass, MeshNormals.Facet, startPosition); + break; + + case "digit": + shape = randomDigit(); + createRigidBody( + shape, totalMass, MeshNormals.Facet, startPosition); + break; + + case "breakableRod": + case "chain": + case "diptych": + case "flail": + case "ragdoll": + createJointed(); + break; + + case "letter": + shape = randomLetter(); + createRigidBody( + shape, totalMass, MeshNormals.Facet, startPosition); + break; + + case "madMallet": + shape = randomMallet(false); + createRigidBody( + shape, totalMass, MeshNormals.Smooth, startPosition); + break; + + case "mallet": + shape = randomMallet(true); + createRigidBody( + shape, totalMass, MeshNormals.Smooth, startPosition); + break; + + case "sphere": + shape = random.nextShape(typeName); + createRigidBody( + shape, totalMass, MeshNormals.Sphere, startPosition); + break; + + case "cloth": + case "squishyBall": + createSoftBody(); + break; + + default: + String message = "typeName = " + MyString.quote(typeName); + throw new IllegalArgumentException(message); + } + } + + /** + * Create a breakable rod. Its length runs along the drop's local Y axis. + * + * @param totalLength the total length (in physics-space units, >0) + * @param radius the radius (in physics-space units, >0) + */ + private void createBreakableRod(float totalLength, float radius) { + float pieceLength = totalLength / 2f; + float pieceMass = totalMass / 2f; + + CylinderCollisionShape pieceShape = new CylinderCollisionShape( + radius, pieceLength, PhysicsSpace.AXIS_Y); + + Transform tmpPosition = new Transform(); + tmpPosition.getTranslation().y = pieceLength / 2; + Vector3f pivotInA = tmpPosition.getTranslation().negate(); + MyMath.combine(tmpPosition, startPosition, tmpPosition); + PhysicsRigidBody a = createRigidBody( + pieceShape, pieceMass, MeshNormals.Smooth, tmpPosition); + + tmpPosition.loadIdentity(); + tmpPosition.getTranslation().y = -pieceLength / 2; + Vector3f pivotInB = tmpPosition.getTranslation().negate(); + MyMath.combine(tmpPosition, startPosition, tmpPosition); + PhysicsRigidBody b = createRigidBody( + pieceShape, pieceMass, MeshNormals.Smooth, tmpPosition); + + Matrix3f rotInA = Matrix3f.IDENTITY; + Matrix3f rotInB = Matrix3f.IDENTITY; + New6Dof fixed = new New6Dof( + a, b, pivotInA, pivotInB, rotInA, rotInB, RotationOrder.YZX); + allJoints.add(fixed); + + fixed.setBreakingImpulseThreshold(4f); + + for (int i = PhysicsSpace.AXIS_X; i <= PhysicsSpace.AXIS_Z; ++i) { + RotationMotor motor = fixed.getRotationMotor(i); + motor.set(MotorParam.LowerLimit, 0f); + motor.set(MotorParam.UpperLimit, 0f); + } + } + + /** + * Create a chain of links. Initially the chain is laid out along the drop's + * local Y axis. + * + * @param numLinks the number of links (>0) + * @param thickness the thickness of each link (in physics-space units, + * >0) + */ + private void createChain(int numLinks, float thickness) { + float halfThickness = thickness / 2f; + float linkIhh = 3f * halfThickness; + float linkIhw = 1.1f * halfThickness; + float angleStep = FastMath.HALF_PI; + float yStep = 1.9f * linkIhh; // center-to-center + float linkMass = totalMass / numLinks; + + CompoundCollisionShape shape + = CompoundTestShapes.makeLink(linkIhh, linkIhw, halfThickness); + + Quaternion rotationStep + = new Quaternion().fromAngles(0f, angleStep, 0f); + + float y0 = -yStep * (numLinks - 1) / 2f; + Vector3f startOffset = new Vector3f(0f, y0, 0f); + Transform transform = new Transform(startOffset); + Vector3f linkLocation = transform.getTranslation(); // alias + Quaternion linkOrientation = transform.getRotation(); // alias + + Transform tmpPosition = new Transform(); + for (int linkIndex = 0; linkIndex < numLinks; ++linkIndex) { + tmpPosition.set(transform); + MyMath.combine(tmpPosition, startPosition, tmpPosition); + createRigidBody( + shape, linkMass, MeshNormals.Smooth, tmpPosition); + + linkLocation.y += yStep; + rotationStep.mult(linkOrientation, linkOrientation); + } + } + + /** + * Create a diptych consisting of 2 thin boxes, joined by a hinge. The axis + * of the hinge is the drop's local Y axis. + * + * @param height the desired height of each box (>0) + * @param thickness the desired thickness of each box (>0) + * @param boxWidth the desired width of each box (>0) + */ + private void createDiptych(float height, float thickness, float boxWidth) { + float boxHalfWidth = boxWidth / 2f; + float halfThickness = thickness / 2f; + CollisionShape boxShape = new BoxCollisionShape( + boxHalfWidth, height / 2f, halfThickness); + + float boxMass = totalMass / 2f; + + Vector3f pivotInA = new Vector3f(boxHalfWidth, 0f, halfThickness); + Transform aPosition = new Transform(); + aPosition.setTranslation(pivotInA.negate()); + MyMath.combine(aPosition, startPosition, aPosition); + + PhysicsRigidBody a = createRigidBody( + boxShape, boxMass, MeshNormals.Facet, aPosition); + + Vector3f pivotInB = new Vector3f(-boxHalfWidth, 0f, halfThickness); + Transform bPosition = new Transform(); + bPosition.setTranslation(pivotInB.negate()); + MyMath.combine(bPosition, startPosition, bPosition); + + PhysicsRigidBody b = createRigidBody( + boxShape, boxMass, MeshNormals.Facet, bPosition); + + Matrix3f rotInA = Matrix3f.IDENTITY; + Matrix3f rotInB = Matrix3f.IDENTITY; + New6Dof hinge = new New6Dof( + a, b, pivotInA, pivotInB, rotInA, rotInB, RotationOrder.YZX); + allJoints.add(hinge); + + RotationMotor xMotor = hinge.getRotationMotor(PhysicsSpace.AXIS_X); + xMotor.set(MotorParam.LowerLimit, 0f); + xMotor.set(MotorParam.UpperLimit, 0f); + + RotationMotor yMotor = hinge.getRotationMotor(PhysicsSpace.AXIS_Y); + yMotor.set(MotorParam.LowerLimit, 0.1f); + yMotor.set(MotorParam.Equilibrium, 1.6f); + yMotor.set(MotorParam.UpperLimit, FastMath.PI); + yMotor.setSpringEnabled(true); + yMotor.set(MotorParam.Stiffness, 0.01f); // a very weak spring + + RotationMotor zMotor = hinge.getRotationMotor(PhysicsSpace.AXIS_Z); + zMotor.set(MotorParam.LowerLimit, 0f); + zMotor.set(MotorParam.UpperLimit, 0f); + } + + /** + * Create a flail consisting of 2 unequal staves, joined by a pivot. + * Initially both staves lie on the drop's local Y axis. + * + * @param aLength the length of the "A" staff (>0) + * @param bLength the length of the "B" staff (>0) + * @param radius the radius of each staff (>0) + */ + private void createFlail(float aLength, float bLength, float radius) { + float linearDensity = totalMass / (aLength + bLength); + + CollisionShape aShape = new CapsuleCollisionShape( + radius, aLength, PhysicsSpace.AXIS_Y); + float aMass = aLength * linearDensity; + + Vector3f pivotInA = new Vector3f(0f, 3f * radius + aLength / 2f, 0f); + Transform aPosition = new Transform(); + aPosition.setTranslation(pivotInA.negate()); + MyMath.combine(aPosition, startPosition, aPosition); + + PhysicsRigidBody a + = createRigidBody(aShape, aMass, MeshNormals.Smooth, aPosition); + + CollisionShape bShape = new CapsuleCollisionShape( + radius, bLength, PhysicsSpace.AXIS_Y); + float bMass = bLength * linearDensity; + + Vector3f pivotInB = new Vector3f(0f, -3f * radius - bLength / 2f, 0f); + Transform bPosition = new Transform(); + bPosition.setTranslation(pivotInB.negate()); + MyMath.combine(bPosition, startPosition, bPosition); + + PhysicsRigidBody b + = createRigidBody(bShape, bMass, MeshNormals.Smooth, bPosition); + + Matrix3f rotInA = Matrix3f.IDENTITY; + Matrix3f rotInB = Matrix3f.IDENTITY; + New6Dof pivot = new New6Dof( + a, b, pivotInA, pivotInB, rotInA, rotInB, RotationOrder.XYZ); + allJoints.add(pivot); + } + + /** + * Create a jointed Drop based on the configuration. + */ + private void createJointed() { + switch (typeName) { + case "breakableRod": { + float totalLength = 5f; + float radius = 1f; + createBreakableRod(totalLength, radius); + break; + } + + case "chain": { + int numLinks = 10; + float thickness = 0.4f; + createChain(numLinks, thickness); + break; + } + + case "diptych": { + float height = 4f; + float thickness = 0.4f; + float boxWidth = 2f; + createDiptych(height, thickness, boxWidth); + break; + } + + case "flail": { + float aLength = 5f; + float bLength = 2.5f; + float radius = 0.2f; + createFlail(aLength, bLength, radius); + break; + } + + case "ragdoll": { + float scale = 5f; + createRagdoll("Models/BaseMesh/BaseMesh.j3o", scale, + new BaseMeshControl()); + break; + } + + default: + String message = "typeName = " + MyString.quote(typeName); + throw new IllegalArgumentException(message); + } + } + + /** + * Create a ragdoll using DynamicAnimControl. + * + * @param assetPath asset path to the model J3O (not null, not empty) + * @param scale the uniform scale factor to apply (>0) + * @param control a configured DynamicAnimControl to use (not null) + */ + private void createRagdoll( + String assetPath, float scale, DynamicAnimControl control) { + assert scale > 0f : scale; + + AssetManager assetManager = appInstance.getAssetManager(); + Node cgModel = (Node) assetManager.loadModel(assetPath); + cgModel.setLocalTransform(startPosition); + cgModel.setLocalScale(scale); + + this.dac = control; + + AbstractControl ac = RagUtils.findSControl(cgModel); + ac.getSpatial().addControl(control); + + PhysicsRigidBody[] bodies = dac.listRigidBodies(); + for (PhysicsRigidBody body : bodies) { + allBodies.add(body); + body.setDebugMeshNormals(MeshNormals.Smooth); + } + + List boneLinks = dac.listLinks(BoneLink.class); + for (BoneLink link : boneLinks) { + PhysicsJoint joint = link.getJoint(); + allJoints.add(joint); + } + } + + /** + * Create a rigid body at the specified position and add it to this Drop. + * + * @param shape (not null) + * @param mass (>0) + * @param debugMeshNormals (not null) + * @param position (not null, unaffected) + * @return the new body (not null, not in any space) + */ + private PhysicsRigidBody createRigidBody(CollisionShape shape, float mass, + MeshNormals debugMeshNormals, Transform position) { + assert shape != null : typeName; + assert mass > 0f : mass; + assert debugMeshNormals != null : typeName; + + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + allBodies.add(result); + + result.setDebugMeshNormals(debugMeshNormals); + if (inverseInertia != null) { + result.setInverseInertiaLocal(inverseInertia); + } + result.setPhysicsLocation(position.getTranslation()); + result.setPhysicsRotation(position.getRotation()); + + return result; + } + + /** + * Create a soft Drop based on the configuration. + */ + private void createSoftBody() { + PhysicsSoftBody softBody; + switch (typeName) { + case "cloth": { + int numLines = 41; + float lineSpacing = 0.3f; // mesh units + Mesh mesh = new ClothGrid(numLines, numLines, lineSpacing); + + softBody = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); + softBody.applyTranslation(startPosition.getTranslation()); + + softBody.setDebugMeshNormals(MeshNormals.Smooth); + softBody.setMargin(lineSpacing); + + SoftBodyConfig config = softBody.getSoftConfig(); + config.setPositionIterations(20); // default = 1 + break; + } + + case "squishyBall": { + int numRefinementIterations = 3; + float radius = 3f; + Mesh mesh = new Icosphere(numRefinementIterations, radius); + + softBody = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); + softBody.applyTransform(startPosition); + + softBody.setDebugMeshNormals(MeshNormals.Smooth); + + boolean setVolumePose = false; + boolean setFramePose = true; + softBody.setPose(setVolumePose, setFramePose); + + SoftBodyConfig config = softBody.getSoftConfig(); + config.set(Sbcp.PoseMatching, 0.1f); + config.setCollisionFlags(ConfigFlag.SDF_RS, ConfigFlag.VF_SS); + config.setPositionIterations(9); + break; + } + + default: + String message = "typeName = " + MyString.quote(typeName); + throw new IllegalArgumentException(message); + } + + softBody.setMass(totalMass); + SoftBodyConfig config = softBody.getSoftConfig(); + config.setCollisionFlags(ConfigFlag.SDF_RS, ConfigFlag.VF_SS); + config.setPositionIterations(3); + + allBodies.add(softBody); + } + + /** + * Pseudo-randomly select the shape of a decimal digit. + * + * @return the pre-existing instance (not null) + */ + private CollisionShape randomDigit() { + Random random = appInstance.getGenerator(); + char glyphChar = (char) ('0' + random.nextInt(10)); + String glyphString = Character.toString(glyphChar); + CollisionShape result = appInstance.findShape(glyphString); + assert result != null : glyphChar; + + return result; + } + + /** + * Pseudo-randomly select the shape of an uppercase letter. + * + * @return the pre-existing instance (not null) + */ + private CollisionShape randomLetter() { + Random random = appInstance.getGenerator(); + char glyphChar = (char) ('A' + random.nextInt(26)); + String glyphString = Character.toString(glyphChar); + CollisionShape result = appInstance.findShape(glyphString); + assert result != null : glyphChar; + + return result; + } + + /** + * Pseudo-randomly generate an asymmetrical compound shape consisting of 2 + * cylinders, a head and a handle. + * + * @param correctAxes if true, correct the shape's center of mass and + * principal axes + * @return a new instance (not null) + */ + private CollisionShape randomMallet(boolean correctAxes) { + Generator random = appInstance.getGenerator(); + float handleR = 0.5f; + float headR = handleR + random.nextFloat(); + float headHalfLength = headR + random.nextFloat(); + float handleHalfLength = headHalfLength + random.nextFloat(0f, 2.5f); + CompoundCollisionShape result = CompoundTestShapes.makeMadMallet( + handleR, headR, handleHalfLength, headHalfLength); + /* + * At this point, the shape's center of mass lies at the bare end + * of the handle: a "mad" mallet that prefers to stand upright. + */ + if (correctAxes) { + float handleMass = 0.15f; + float headMass = 1f - handleMass; // Put 85% of mass in the head. + FloatBuffer masses + = BufferUtils.createFloatBuffer(handleMass, headMass); + + Vector3f inertia = new Vector3f(); + Transform transform = result.principalAxes(masses, null, inertia); + this.inverseInertia = Vector3f.UNIT_XYZ.divide(inertia); + result.correctAxes(transform); + } + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/DropTest.java b/MinieExamples/src/main/java/jme3utilities/minie/test/DropTest.java index 3720f6c2d..7a4cf435e 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/DropTest.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/DropTest.java @@ -1,961 +1,961 @@ -/* - Copyright (c) 2018-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.ConfigFlag; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.util.BufferUtils; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Deque; -import java.util.List; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.BackingStoreException; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.math.noise.Generator; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.mesh.ClothHexagon; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; - -/** - * Test/demonstrate various collision shapes by dropping drops - * (small/dynamic/rigid bodies) onto a platform (large/fixed/horizontal body). - *

- * Collision objects are rendered entirely by debug visualization. - *

- * Seen in the November 2018 demo video: - * https://www.youtube.com/watch?v=OS2zjB01c6E - * - * @author Stephen Gold sgold@sonic.net - */ -public class DropTest - extends PhysicsDemo - implements DebugInitListener { - // ************************************************************************* - // constants and loggers - - /** - * approximate Y coordinate for the main surface of the platform (in - * physics-space coordinates) - */ - final private static float platformSurfaceY = 0f; - /** - * upper limit on the number of drops - */ - final private static int maxNumDrops = 80; - /** - * number of colors/materials for drops - */ - final private static int numDropColors = 4; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(DropTest.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = DropTest.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * current drops, in order of creation - */ - final private static Deque drops = new ArrayDeque<>(maxNumDrops); - /** - * selected drop, or null if none - */ - private static Drop selectedDrop = null; - /** - * AppState to manage the status overlay - */ - private static DropTestStatus status; - /** - * AppState to manage the PhysicsSpace - */ - private static SoftPhysicsAppState bulletAppState; - // ************************************************************************* - // constructors - - /** - * Instantiate the DropTest application. - */ - public DropTest() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Count how many rigid bodies are active. - * - * @return the count (≥0) - */ - int countActive() { - int result = 0; - Collection rigidBodies - = getPhysicsSpace().getRigidBodyList(); - for (PhysicsRigidBody rigidBody : rigidBodies) { - if (rigidBody.isActive()) { - ++result; - } - } - - return result; - } - - /** - * Count how many drops are in the PhysicsSpace. - * - * @return the count (≥0) - */ - static int countDrops() { - int result = drops.size(); - return result; - } - - /** - * Main entry point for the DropTest application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Enable direct-memory tracking. - BufferUtils.setTrackDirectMemoryEnabled(true); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - try { - settings.load(applicationName); - } catch (BackingStoreException exception) { - logger.warning("Failed to load AppSettings."); - } - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new DropTest(); - application.setSettings(settings); - application.start(); - } - - /** - * Restart the current scenario. - */ - void restartScenario() { - for (Drop drop : drops) { - drop.removeFromSpace(); - } - selectDrop(null); - drops.clear(); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.destroy(); - assert physicsSpace.isEmpty(); - - String platformName = status.platformType(); - addPlatform(platformName, platformSurfaceY); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - status = new DropTestStatus(); - boolean success = stateManager.attach(status); - assert success; - - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - generateShapes(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - String platformName = status.platformType(); - addPlatform(platformName, platformSurfaceY); - - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - renderer.setDefaultAnisotropicFilter(degree); - - addADrop(1); - } - - /** - * Add a platform to the PhysicsSpace. - * - * @param platformName the name of the desired platform type (not null) - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - */ - @Override - public void addPlatform(String platformName, float topY) { - switch (platformName) { - case "bedOfNails": - case "corner": - case "sieve": - case "tray": - addPlatform(platformName, MeshNormals.Facet, topY); - break; - - case "candyDish": - case "dimples": - case "smooth": - addPlatform(platformName, MeshNormals.Smooth, topY); - break; - - case "trampoline": - addTrampoline(topY); - break; - - default: - super.addPlatform(platformName, topY); - } - } - - /** - * Calculate screen bounds for the detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes below the status. - float margin = 10f; // in pixels - float leftX = margin; - float topY = viewPortHeight - 160f - margin; - float width = viewPortWidth - leftX - margin; - float height = topY - margin; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - // shiny, lit material for the selected drop - ColorRGBA lightGray = new ColorRGBA(0.6f, 0.6f, 0.6f, 1f); - Material selected - = MyAsset.createShinyMaterial(assetManager, lightGray); - selected.setFloat("Shininess", 15f); - registerMaterial("selected", selected); - - // shiny, lit materials for dynamic bodies in drops - ColorRGBA[] dropColors = new ColorRGBA[numDropColors]; - dropColors[0] = new ColorRGBA(0.2f, 0f, 0f, 1f); // ruby - dropColors[1] = new ColorRGBA(0f, 0.07f, 0f, 1f); // emerald - dropColors[2] = new ColorRGBA(0f, 0f, 0.3f, 1f); // sapphire - dropColors[3] = new ColorRGBA(0.2f, 0.1f, 0f, 1f); // topaz - - for (int index = 0; index < dropColors.length; ++index) { - ColorRGBA color = dropColors[index]; - Material material - = MyAsset.createShinyMaterial(assetManager, color); - material.setFloat("Shininess", 15f); - RenderState additional = material.getAdditionalRenderState(); - additional.setFaceCullMode(RenderState.FaceCullMode.Off); - - registerMaterial("drop" + index, material); - } - } - - /** - * Initialize the library of named collision shapes during startup. - */ - @Override - public void generateShapes() { - super.generateShapes(); - CollisionShape shape; - - // "ankh" using manual decomposition - String ankhPath = "CollisionShapes/ankh.j3o"; - shape = (CollisionShape) assetManager.loadAsset(ankhPath); - registerShape("ankh", shape); - - // "banana" using manual decomposition - String bananaPath = "CollisionShapes/banana.j3o"; - shape = (CollisionShape) assetManager.loadAsset(bananaPath); - registerShape("banana", shape); - - // "barrel" - String barrelPath = "CollisionShapes/barrel.j3o"; - shape = (CollisionShape) assetManager.loadAsset(barrelPath); - shape.setScale(3f); - registerShape("barrel", shape); - - // "bowlingPin" using manual decomposition - String bowlingPinPath = "CollisionShapes/bowlingPin.j3o"; - shape = (CollisionShape) assetManager.loadAsset(bowlingPinPath); - registerShape("bowlingPin", shape); - - // "candyDish" - String candyDishPath = "Models/CandyDish/CandyDish.j3o"; - Node candyDishNode = (Node) assetManager.loadModel(candyDishPath); - Geometry candyDishGeometry = (Geometry) candyDishNode.getChild(0); - Mesh candyDishMesh = candyDishGeometry.getMesh(); - shape = new MeshCollisionShape(candyDishMesh); - shape.setScale(5f); - registerShape("candyDish", shape); - - // "duck" using V-HACD - String duckPath = "CollisionShapes/duck.j3o"; - shape = (CollisionShape) assetManager.loadAsset(duckPath); - shape.setScale(2f); - registerShape("duck", shape); - - // "heart" - String heartPath = "CollisionShapes/heart.j3o"; - shape = (CollisionShape) assetManager.loadAsset(heartPath); - shape.setScale(1.2f); - registerShape("heart", shape); - - // "horseshoe" using manual decomposition - String horseshoePath = "CollisionShapes/horseshoe.j3o"; - shape = (CollisionShape) assetManager.loadAsset(horseshoePath); - registerShape("horseshoe", shape); - - // "sword" using V-HACD - String swordPath = "CollisionShapes/sword.j3o"; - shape = (CollisionShape) assetManager.loadAsset(swordPath); - shape.setScale(5f); - registerShape("sword", shape); - - // "teapot" using V-HACD - String teapotPath = "CollisionShapes/teapot.j3o"; - shape = (CollisionShape) assetManager.loadAsset(teapotPath); - shape.setScale(3f); - registerShape("teapot", shape); - - // "teapot" using GImpact - String teapotGiPath = "CollisionShapes/teapotGi.j3o"; - shape = (CollisionShape) assetManager.loadAsset(teapotGiPath); - shape.setScale(3f); - registerShape("teapotGi", shape); - - // letter shapes - for (char character = 'A'; character <= 'Z'; ++character) { - char[] array = {character}; - String glyphString = new String(array); - String assetPath = String.format( - "CollisionShapes/glyphs/%s.j3o", glyphString); - shape = (CollisionShape) assetManager.loadAsset(assetPath); - registerShape(glyphString, shape); - } - - // digit shapes - for (char character = '0'; character <= '9'; ++character) { - char[] array = {character}; - String glyphString = new String(array); - String assetPath = String.format( - "CollisionShapes/glyphs/%s.j3o", glyphString); - shape = (CollisionShape) assetManager.loadAsset(assetPath); - registerShape(glyphString, shape); - } - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind("add", KeyInput.KEY_RETURN, KeyInput.KEY_INSERT, - KeyInput.KEY_NUMPAD0, KeyInput.KEY_SPACE); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - - dim.bind("delete last", KeyInput.KEY_BACK, KeyInput.KEY_SUBTRACT); - dim.bind("delete selected", KeyInput.KEY_DECIMAL, KeyInput.KEY_DELETE); - - dim.bind("dump selected", KeyInput.KEY_LBRACKET); - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind("next field", KeyInput.KEY_NUMPAD2); - dim.bind("next value", KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); - - dim.bind("pick", "RMB"); - dim.bind("pick", KeyInput.KEY_R); - - dim.bind("pop selected", KeyInput.KEY_PGUP); - - dim.bind("previous field", KeyInput.KEY_NUMPAD8); - dim.bind("previous value", KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); - - dim.bind("restart", KeyInput.KEY_NUMPAD5); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - dim.bindSignal("shower", KeyInput.KEY_ADD, KeyInput.KEY_I); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleCcdSpheres, KeyInput.KEY_L); - dim.bind("toggle childColor", KeyInput.KEY_COMMA); - dim.bind(asToggleGArrows, KeyInput.KEY_J); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - dim.bind(asToggleWArrows, KeyInput.KEY_N); - dim.bind("toggle wireframe", KeyInput.KEY_SLASH); - - dim.bind("value+7", KeyInput.KEY_NUMPAD9); - dim.bind("value-7", KeyInput.KEY_NUMPAD7); - } - - /** - * Process an action that wasn't handled by the active InputMode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "add": - addADrop(3); - return; - - case "delete last": - deleteLastDrop(); - return; - case "delete selected": - deleteSelected(); - return; - - case "dump selected": - dumpSelected(); - return; - - case "next field": - status.advanceSelectedField(+1); - return; - case "next value": - status.advanceValue(+1); - return; - - case "pick": - pick(); - return; - case "pop selected": - popSelected(); - return; - - case "previous field": - status.advanceSelectedField(-1); - return; - case "previous value": - status.advanceValue(-1); - return; - - case "restart": - restartScenario(); - return; - - case "toggle childColor": - status.toggleChildColor(); - setDebugMaterialsAll(); - return; - case "toggle wireframe": - status.toggleWireframe(); - setDebugMaterialsAll(); - setDebugShadowMode(); - return; - - case "value+7": - status.advanceValue(+7); - return; - case "value-7": - status.advanceValue(-7); - return; - - default: - } - } - - // The action is not handled: forward it to the superclass. - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - status.resize(newWidth, newHeight); - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked after adding a collision object to the PhysicsSpace. - * - * @param pco the object that was added (not null) - */ - @Override - public void postAdd(PhysicsCollisionObject pco) { - Object appData = pco.getApplicationData(); - if (appData == null) { - Random random = getGenerator(); - String materialName = "drop" + random.nextInt(numDropColors); - Material debugMaterial = findMaterial(materialName); - assert debugMaterial != null : materialName; - pco.setApplicationData(debugMaterial); - } - - CollisionShape shape = pco.getCollisionShape(); - if (!pco.isStatic() && shape != null) { - pco.setCcdMotionThreshold(5f); - - float sweptSphereRadius = shape.maxRadius(); - pco.setCcdSweptSphereRadius(sweptSphereRadius); - } - - if (pco instanceof PhysicsRigidBody) { - PhysicsRigidBody rigidBody = (PhysicsRigidBody) pco; - - float damping = status.damping(); - rigidBody.setDamping(damping, damping); - - rigidBody.setSleepingThresholds(0.1f, 0.1f); - } - - if (!(shape instanceof CompoundCollisionShape)) { - pco.setDebugMeshResolution(DebugShapeFactory.highResolution); - } - - float friction = status.friction(); - pco.setFriction(friction); - - float restitution = status.restitution(); - pco.setRestitution(restitution); - - setDebugMaterial(pco); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - for (Drop drop : drops) { - drop.update(); - } - - Signals signals = getSignals(); - if (signals.test("shower")) { - addADrop(4); - } - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - // ************************************************************************* - // private methods - - /** - * Add a drop (dynamic body) to the PhysicsSpace. Note: recursive. - * - * @param numTries the number of attempts to make before giving up (>0) - * @return true if successful, otherwise false - */ - private boolean addADrop(int numTries) { - if (countDrops() >= maxNumDrops) { - return false; // too many drops - } - - Generator random = getGenerator(); - Vector3f startLocation = random.nextVector3f(); //TODO garbage - startLocation.multLocal(2.5f, 5f, 2.5f); - startLocation.y += 20f; - Quaternion startOrientation = random.nextQuaternion(); //TODO garbage - Transform startPosition - = new Transform(startLocation, startOrientation); //TODO garbage - - String typeName = status.nextDropType(); - float totalMass = 1f; - Drop drop = new Drop(this, typeName, totalMass, startPosition); - - if (drop.hasDac() || !drop.hasHullContacts()) { - drop.addToSpace(); - drops.addLast(drop); - return true; - - } else if (numTries > 1) { // try again - boolean result = addADrop(numTries - 1); - return result; - } - - return false; - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); - Vector3f direction = new Vector3f(1f, -3f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - rootSpatial.addLight(sun); - sun.setName("sun"); - - viewPort.clearProcessors(); - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.7f); - viewPort.addProcessor(dlsr); - } - - /** - * Add a hexagonal trampoline to the PhysicsSpace, to serve as a platform. - * - * @param y the initial Y coordinate - */ - private void addTrampoline(float y) { - int numRings = 9; - float vertexSpacing = 2f; - Mesh mesh = new ClothHexagon(numRings, vertexSpacing); - PhysicsSoftBody softBody = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); - softBody.applyTranslation(new Vector3f(0f, y, 0f)); - - // Pin every node on the perimeter. - int numNodes = mesh.getVertexCount(); - int numInteriorNodes = 1 + 3 * numRings * (numRings - 1); - for (int nodeI = numInteriorNodes; nodeI < numNodes; ++nodeI) { - softBody.setNodeMass(nodeI, PhysicsBody.massForStatic); - } - - softBody.setDebugMeshNormals(MeshNormals.Smooth); - softBody.setMargin(1f); - softBody.setMass(100f); - - SoftBodyConfig config = softBody.getSoftConfig(); - config.setCollisionFlags(ConfigFlag.SDF_RS, ConfigFlag.VF_SS); - config.setPositionIterations(3); - - addPlatform(softBody); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.1f; - float far = 500f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(10f); - flyCam.setZoomSpeed(10f); - - cam.setLocation(new Vector3f(0f, platformSurfaceY + 20f, 40f)); - cam.setRotation(new Quaternion(0f, 0.9649f, -0.263f, 0f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - DebugShapeFactory.setIndexBuffers(200); - - bulletAppState = new SoftPhysicsAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugInitListener(this); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - stateManager.attach(bulletAppState); - - float gravity = status.gravity(); - setGravityAll(gravity); - } - - /** - * Delete the most recently added drop. - */ - private void deleteLastDrop() { - Drop lastDrop = drops.peekLast(); - if (lastDrop != null) { - lastDrop.removeFromSpace(); - if (lastDrop == selectedDrop) { - selectDrop(null); - } - drops.removeLast(); - activateAll(); - } - } - - /** - * Delete the selected drop, if any. - */ - private void deleteSelected() { - if (selectedDrop != null) { - selectedDrop.removeFromSpace(); - boolean success = drops.remove(selectedDrop); - assert success; - selectDrop(null); - activateAll(); - } - } - - /** - * Dump the selected drop, if any. - */ - private void dumpSelected() { - if (selectedDrop == null) { - System.out.printf("%nNo drop selected."); - } else { - PhysicsDumper dumper = getDumper(); - PhysicsSpace space = getPhysicsSpace(); - dumper.dump(space, "", selectedDrop); - } - } - - /** - * Cast a physics ray from the cursor and select the nearest drop in the - * result. - */ - private void pick() { - List hits = rayTestCursor(); - for (PhysicsRayTestResult hit : hits) { - PhysicsCollisionObject pco = hit.getCollisionObject(); - for (Drop drop : drops) { - if (drop.displayObject(pco)) { - selectDrop(drop); - return; - } - } - } - selectDrop(null); - } - - /** - * Apply an upward impulse to the selected drop. - */ - private static void popSelected() { - if (selectedDrop != null) { - float gravity = status.gravity(); - float deltaV = FastMath.sqrt(30f * gravity); - selectedDrop.pop(deltaV); - } - } - - /** - * Alter which Drop is selected. - * - * @param newDrop the Drop to select (alias created) or null for none - */ - private void selectDrop(Drop newDrop) { - if (newDrop != selectedDrop) { - selectedDrop = newDrop; - setDebugMaterialsAll(); - } - } - - /** - * Update the debug materials of the specified collision object. - * - * @param pco the object to update (not null, modified) - */ - private void setDebugMaterial(PhysicsCollisionObject pco) { - CollisionShape shape = pco.getCollisionShape(); - - Material debugMaterial; - if (selectedDrop != null && selectedDrop.displayObject(pco)) { - debugMaterial = findMaterial("selected"); - - } else if (status.isWireframe()) { - debugMaterial = null; - - } else if (status.isChildColoring() - && shape instanceof CompoundCollisionShape) { - debugMaterial = BulletDebugAppState.enableChildColoring; - - } else { - // Use the pre-selected lit material. - debugMaterial = (Material) pco.getApplicationData(); - } - - pco.setDebugMaterial(debugMaterial); - } - - /** - * Update the debug materials of all collision objects. - */ - private void setDebugMaterialsAll() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { - setDebugMaterial(pco); - } - } - - /** - * Update the ShadowMode of the debug scene. - */ - private static void setDebugShadowMode() { - RenderQueue.ShadowMode mode; - if (status.isWireframe()) { - mode = RenderQueue.ShadowMode.Off; - } else { - mode = RenderQueue.ShadowMode.CastAndReceive; - } - bulletAppState.setDebugShadowMode(mode); - } -} +/* + Copyright (c) 2018-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.ConfigFlag; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.util.BufferUtils; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.BackingStoreException; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.math.noise.Generator; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.mesh.ClothHexagon; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; + +/** + * Test/demonstrate various collision shapes by dropping drops + * (small/dynamic/rigid bodies) onto a platform (large/fixed/horizontal body). + *

+ * Collision objects are rendered entirely by debug visualization. + *

+ * Seen in the November 2018 demo video: + * https://www.youtube.com/watch?v=OS2zjB01c6E + * + * @author Stephen Gold sgold@sonic.net + */ +public class DropTest + extends PhysicsDemo + implements DebugInitListener { + // ************************************************************************* + // constants and loggers + + /** + * approximate Y coordinate for the main surface of the platform (in + * physics-space coordinates) + */ + final private static float platformSurfaceY = 0f; + /** + * upper limit on the number of drops + */ + final private static int maxNumDrops = 80; + /** + * number of colors/materials for drops + */ + final private static int numDropColors = 4; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(DropTest.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = DropTest.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * current drops, in order of creation + */ + final private static Deque drops = new ArrayDeque<>(maxNumDrops); + /** + * selected drop, or null if none + */ + private static Drop selectedDrop = null; + /** + * AppState to manage the status overlay + */ + private static DropTestStatus status; + /** + * AppState to manage the PhysicsSpace + */ + private static SoftPhysicsAppState bulletAppState; + // ************************************************************************* + // constructors + + /** + * Instantiate the DropTest application. + */ + public DropTest() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Count how many rigid bodies are active. + * + * @return the count (≥0) + */ + int countActive() { + int result = 0; + Collection rigidBodies + = getPhysicsSpace().getRigidBodyList(); + for (PhysicsRigidBody rigidBody : rigidBodies) { + if (rigidBody.isActive()) { + ++result; + } + } + + return result; + } + + /** + * Count how many drops are in the PhysicsSpace. + * + * @return the count (≥0) + */ + static int countDrops() { + int result = drops.size(); + return result; + } + + /** + * Main entry point for the DropTest application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Enable direct-memory tracking. + BufferUtils.setTrackDirectMemoryEnabled(true); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + try { + settings.load(applicationName); + } catch (BackingStoreException exception) { + logger.warning("Failed to load AppSettings."); + } + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new DropTest(); + application.setSettings(settings); + application.start(); + } + + /** + * Restart the current scenario. + */ + void restartScenario() { + for (Drop drop : drops) { + drop.removeFromSpace(); + } + selectDrop(null); + drops.clear(); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.destroy(); + assert physicsSpace.isEmpty(); + + String platformName = status.platformType(); + addPlatform(platformName, platformSurfaceY); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + status = new DropTestStatus(); + boolean success = stateManager.attach(status); + assert success; + + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + generateShapes(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + String platformName = status.platformType(); + addPlatform(platformName, platformSurfaceY); + + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + renderer.setDefaultAnisotropicFilter(degree); + + addADrop(1); + } + + /** + * Add a platform to the PhysicsSpace. + * + * @param platformName the name of the desired platform type (not null) + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + */ + @Override + public void addPlatform(String platformName, float topY) { + switch (platformName) { + case "bedOfNails": + case "corner": + case "sieve": + case "tray": + addPlatform(platformName, MeshNormals.Facet, topY); + break; + + case "candyDish": + case "dimples": + case "smooth": + addPlatform(platformName, MeshNormals.Smooth, topY); + break; + + case "trampoline": + addTrampoline(topY); + break; + + default: + super.addPlatform(platformName, topY); + } + } + + /** + * Calculate screen bounds for the detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes below the status. + float margin = 10f; // in pixels + float leftX = margin; + float topY = viewPortHeight - 160f - margin; + float width = viewPortWidth - leftX - margin; + float height = topY - margin; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + // shiny, lit material for the selected drop + ColorRGBA lightGray = new ColorRGBA(0.6f, 0.6f, 0.6f, 1f); + Material selected + = MyAsset.createShinyMaterial(assetManager, lightGray); + selected.setFloat("Shininess", 15f); + registerMaterial("selected", selected); + + // shiny, lit materials for dynamic bodies in drops + ColorRGBA[] dropColors = new ColorRGBA[numDropColors]; + dropColors[0] = new ColorRGBA(0.2f, 0f, 0f, 1f); // ruby + dropColors[1] = new ColorRGBA(0f, 0.07f, 0f, 1f); // emerald + dropColors[2] = new ColorRGBA(0f, 0f, 0.3f, 1f); // sapphire + dropColors[3] = new ColorRGBA(0.2f, 0.1f, 0f, 1f); // topaz + + for (int index = 0; index < dropColors.length; ++index) { + ColorRGBA color = dropColors[index]; + Material material + = MyAsset.createShinyMaterial(assetManager, color); + material.setFloat("Shininess", 15f); + RenderState additional = material.getAdditionalRenderState(); + additional.setFaceCullMode(RenderState.FaceCullMode.Off); + + registerMaterial("drop" + index, material); + } + } + + /** + * Initialize the library of named collision shapes during startup. + */ + @Override + public void generateShapes() { + super.generateShapes(); + CollisionShape shape; + + // "ankh" using manual decomposition + String ankhPath = "CollisionShapes/ankh.j3o"; + shape = (CollisionShape) assetManager.loadAsset(ankhPath); + registerShape("ankh", shape); + + // "banana" using manual decomposition + String bananaPath = "CollisionShapes/banana.j3o"; + shape = (CollisionShape) assetManager.loadAsset(bananaPath); + registerShape("banana", shape); + + // "barrel" + String barrelPath = "CollisionShapes/barrel.j3o"; + shape = (CollisionShape) assetManager.loadAsset(barrelPath); + shape.setScale(3f); + registerShape("barrel", shape); + + // "bowlingPin" using manual decomposition + String bowlingPinPath = "CollisionShapes/bowlingPin.j3o"; + shape = (CollisionShape) assetManager.loadAsset(bowlingPinPath); + registerShape("bowlingPin", shape); + + // "candyDish" + String candyDishPath = "Models/CandyDish/CandyDish.j3o"; + Node candyDishNode = (Node) assetManager.loadModel(candyDishPath); + Geometry candyDishGeometry = (Geometry) candyDishNode.getChild(0); + Mesh candyDishMesh = candyDishGeometry.getMesh(); + shape = new MeshCollisionShape(candyDishMesh); + shape.setScale(5f); + registerShape("candyDish", shape); + + // "duck" using V-HACD + String duckPath = "CollisionShapes/duck.j3o"; + shape = (CollisionShape) assetManager.loadAsset(duckPath); + shape.setScale(2f); + registerShape("duck", shape); + + // "heart" + String heartPath = "CollisionShapes/heart.j3o"; + shape = (CollisionShape) assetManager.loadAsset(heartPath); + shape.setScale(1.2f); + registerShape("heart", shape); + + // "horseshoe" using manual decomposition + String horseshoePath = "CollisionShapes/horseshoe.j3o"; + shape = (CollisionShape) assetManager.loadAsset(horseshoePath); + registerShape("horseshoe", shape); + + // "sword" using V-HACD + String swordPath = "CollisionShapes/sword.j3o"; + shape = (CollisionShape) assetManager.loadAsset(swordPath); + shape.setScale(5f); + registerShape("sword", shape); + + // "teapot" using V-HACD + String teapotPath = "CollisionShapes/teapot.j3o"; + shape = (CollisionShape) assetManager.loadAsset(teapotPath); + shape.setScale(3f); + registerShape("teapot", shape); + + // "teapot" using GImpact + String teapotGiPath = "CollisionShapes/teapotGi.j3o"; + shape = (CollisionShape) assetManager.loadAsset(teapotGiPath); + shape.setScale(3f); + registerShape("teapotGi", shape); + + // letter shapes + for (char character = 'A'; character <= 'Z'; ++character) { + char[] array = {character}; + String glyphString = new String(array); + String assetPath = String.format( + "CollisionShapes/glyphs/%s.j3o", glyphString); + shape = (CollisionShape) assetManager.loadAsset(assetPath); + registerShape(glyphString, shape); + } + + // digit shapes + for (char character = '0'; character <= '9'; ++character) { + char[] array = {character}; + String glyphString = new String(array); + String assetPath = String.format( + "CollisionShapes/glyphs/%s.j3o", glyphString); + shape = (CollisionShape) assetManager.loadAsset(assetPath); + registerShape(glyphString, shape); + } + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind("add", KeyInput.KEY_RETURN, KeyInput.KEY_INSERT, + KeyInput.KEY_NUMPAD0, KeyInput.KEY_SPACE); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + + dim.bind("delete last", KeyInput.KEY_BACK, KeyInput.KEY_SUBTRACT); + dim.bind("delete selected", KeyInput.KEY_DECIMAL, KeyInput.KEY_DELETE); + + dim.bind("dump selected", KeyInput.KEY_LBRACKET); + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind("next field", KeyInput.KEY_NUMPAD2); + dim.bind("next value", KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); + + dim.bind("pick", "RMB"); + dim.bind("pick", KeyInput.KEY_R); + + dim.bind("pop selected", KeyInput.KEY_PGUP); + + dim.bind("previous field", KeyInput.KEY_NUMPAD8); + dim.bind("previous value", KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); + + dim.bind("restart", KeyInput.KEY_NUMPAD5); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + dim.bindSignal("shower", KeyInput.KEY_ADD, KeyInput.KEY_I); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleCcdSpheres, KeyInput.KEY_L); + dim.bind("toggle childColor", KeyInput.KEY_COMMA); + dim.bind(asToggleGArrows, KeyInput.KEY_J); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + dim.bind(asToggleWArrows, KeyInput.KEY_N); + dim.bind("toggle wireframe", KeyInput.KEY_SLASH); + + dim.bind("value+7", KeyInput.KEY_NUMPAD9); + dim.bind("value-7", KeyInput.KEY_NUMPAD7); + } + + /** + * Process an action that wasn't handled by the active InputMode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "add": + addADrop(3); + return; + + case "delete last": + deleteLastDrop(); + return; + case "delete selected": + deleteSelected(); + return; + + case "dump selected": + dumpSelected(); + return; + + case "next field": + status.advanceSelectedField(+1); + return; + case "next value": + status.advanceValue(+1); + return; + + case "pick": + pick(); + return; + case "pop selected": + popSelected(); + return; + + case "previous field": + status.advanceSelectedField(-1); + return; + case "previous value": + status.advanceValue(-1); + return; + + case "restart": + restartScenario(); + return; + + case "toggle childColor": + status.toggleChildColor(); + setDebugMaterialsAll(); + return; + case "toggle wireframe": + status.toggleWireframe(); + setDebugMaterialsAll(); + setDebugShadowMode(); + return; + + case "value+7": + status.advanceValue(+7); + return; + case "value-7": + status.advanceValue(-7); + return; + + default: + } + } + + // The action is not handled: forward it to the superclass. + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + status.resize(newWidth, newHeight); + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked after adding a collision object to the PhysicsSpace. + * + * @param pco the object that was added (not null) + */ + @Override + public void postAdd(PhysicsCollisionObject pco) { + Object appData = pco.getApplicationData(); + if (appData == null) { + Random random = getGenerator(); + String materialName = "drop" + random.nextInt(numDropColors); + Material debugMaterial = findMaterial(materialName); + assert debugMaterial != null : materialName; + pco.setApplicationData(debugMaterial); + } + + CollisionShape shape = pco.getCollisionShape(); + if (!pco.isStatic() && shape != null) { + pco.setCcdMotionThreshold(5f); + + float sweptSphereRadius = shape.maxRadius(); + pco.setCcdSweptSphereRadius(sweptSphereRadius); + } + + if (pco instanceof PhysicsRigidBody) { + PhysicsRigidBody rigidBody = (PhysicsRigidBody) pco; + + float damping = status.damping(); + rigidBody.setDamping(damping, damping); + + rigidBody.setSleepingThresholds(0.1f, 0.1f); + } + + if (!(shape instanceof CompoundCollisionShape)) { + pco.setDebugMeshResolution(DebugShapeFactory.highResolution); + } + + float friction = status.friction(); + pco.setFriction(friction); + + float restitution = status.restitution(); + pco.setRestitution(restitution); + + setDebugMaterial(pco); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + for (Drop drop : drops) { + drop.update(); + } + + Signals signals = getSignals(); + if (signals.test("shower")) { + addADrop(4); + } + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + // ************************************************************************* + // private methods + + /** + * Add a drop (dynamic body) to the PhysicsSpace. Note: recursive. + * + * @param numTries the number of attempts to make before giving up (>0) + * @return true if successful, otherwise false + */ + private boolean addADrop(int numTries) { + if (countDrops() >= maxNumDrops) { + return false; // too many drops + } + + Generator random = getGenerator(); + Vector3f startLocation = random.nextVector3f(); //TODO garbage + startLocation.multLocal(2.5f, 5f, 2.5f); + startLocation.y += 20f; + Quaternion startOrientation = random.nextQuaternion(); //TODO garbage + Transform startPosition + = new Transform(startLocation, startOrientation); //TODO garbage + + String typeName = status.nextDropType(); + float totalMass = 1f; + Drop drop = new Drop(this, typeName, totalMass, startPosition); + + if (drop.hasDac() || !drop.hasHullContacts()) { + drop.addToSpace(); + drops.addLast(drop); + return true; + + } else if (numTries > 1) { // try again + boolean result = addADrop(numTries - 1); + return result; + } + + return false; + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); + Vector3f direction = new Vector3f(1f, -3f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + rootSpatial.addLight(sun); + sun.setName("sun"); + + viewPort.clearProcessors(); + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.7f); + viewPort.addProcessor(dlsr); + } + + /** + * Add a hexagonal trampoline to the PhysicsSpace, to serve as a platform. + * + * @param y the initial Y coordinate + */ + private void addTrampoline(float y) { + int numRings = 9; + float vertexSpacing = 2f; + Mesh mesh = new ClothHexagon(numRings, vertexSpacing); + PhysicsSoftBody softBody = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); + softBody.applyTranslation(new Vector3f(0f, y, 0f)); + + // Pin every node on the perimeter. + int numNodes = mesh.getVertexCount(); + int numInteriorNodes = 1 + 3 * numRings * (numRings - 1); + for (int nodeI = numInteriorNodes; nodeI < numNodes; ++nodeI) { + softBody.setNodeMass(nodeI, PhysicsBody.massForStatic); + } + + softBody.setDebugMeshNormals(MeshNormals.Smooth); + softBody.setMargin(1f); + softBody.setMass(100f); + + SoftBodyConfig config = softBody.getSoftConfig(); + config.setCollisionFlags(ConfigFlag.SDF_RS, ConfigFlag.VF_SS); + config.setPositionIterations(3); + + addPlatform(softBody); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.1f; + float far = 500f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(10f); + flyCam.setZoomSpeed(10f); + + cam.setLocation(new Vector3f(0f, platformSurfaceY + 20f, 40f)); + cam.setRotation(new Quaternion(0f, 0.9649f, -0.263f, 0f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + DebugShapeFactory.setIndexBuffers(200); + + bulletAppState = new SoftPhysicsAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugInitListener(this); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + stateManager.attach(bulletAppState); + + float gravity = status.gravity(); + setGravityAll(gravity); + } + + /** + * Delete the most recently added drop. + */ + private void deleteLastDrop() { + Drop lastDrop = drops.peekLast(); + if (lastDrop != null) { + lastDrop.removeFromSpace(); + if (lastDrop == selectedDrop) { + selectDrop(null); + } + drops.removeLast(); + activateAll(); + } + } + + /** + * Delete the selected drop, if any. + */ + private void deleteSelected() { + if (selectedDrop != null) { + selectedDrop.removeFromSpace(); + boolean success = drops.remove(selectedDrop); + assert success; + selectDrop(null); + activateAll(); + } + } + + /** + * Dump the selected drop, if any. + */ + private void dumpSelected() { + if (selectedDrop == null) { + System.out.printf("%nNo drop selected."); + } else { + PhysicsDumper dumper = getDumper(); + PhysicsSpace space = getPhysicsSpace(); + dumper.dump(space, "", selectedDrop); + } + } + + /** + * Cast a physics ray from the cursor and select the nearest drop in the + * result. + */ + private void pick() { + List hits = rayTestCursor(); + for (PhysicsRayTestResult hit : hits) { + PhysicsCollisionObject pco = hit.getCollisionObject(); + for (Drop drop : drops) { + if (drop.displayObject(pco)) { + selectDrop(drop); + return; + } + } + } + selectDrop(null); + } + + /** + * Apply an upward impulse to the selected drop. + */ + private static void popSelected() { + if (selectedDrop != null) { + float gravity = status.gravity(); + float deltaV = FastMath.sqrt(30f * gravity); + selectedDrop.pop(deltaV); + } + } + + /** + * Alter which Drop is selected. + * + * @param newDrop the Drop to select (alias created) or null for none + */ + private void selectDrop(Drop newDrop) { + if (newDrop != selectedDrop) { + selectedDrop = newDrop; + setDebugMaterialsAll(); + } + } + + /** + * Update the debug materials of the specified collision object. + * + * @param pco the object to update (not null, modified) + */ + private void setDebugMaterial(PhysicsCollisionObject pco) { + CollisionShape shape = pco.getCollisionShape(); + + Material debugMaterial; + if (selectedDrop != null && selectedDrop.displayObject(pco)) { + debugMaterial = findMaterial("selected"); + + } else if (status.isWireframe()) { + debugMaterial = null; + + } else if (status.isChildColoring() + && shape instanceof CompoundCollisionShape) { + debugMaterial = BulletDebugAppState.enableChildColoring; + + } else { + // Use the pre-selected lit material. + debugMaterial = (Material) pco.getApplicationData(); + } + + pco.setDebugMaterial(debugMaterial); + } + + /** + * Update the debug materials of all collision objects. + */ + private void setDebugMaterialsAll() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { + setDebugMaterial(pco); + } + } + + /** + * Update the ShadowMode of the debug scene. + */ + private static void setDebugShadowMode() { + RenderQueue.ShadowMode mode; + if (status.isWireframe()) { + mode = RenderQueue.ShadowMode.Off; + } else { + mode = RenderQueue.ShadowMode.CastAndReceive; + } + bulletAppState.setDebugShadowMode(mode); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/DropTestStatus.java b/MinieExamples/src/main/java/jme3utilities/minie/test/DropTestStatus.java index 56c7d39b8..ee8b8a861 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/DropTestStatus.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/DropTestStatus.java @@ -1,556 +1,556 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.math.ColorRGBA; -import java.util.Arrays; -import java.util.logging.Logger; -import jme3utilities.SimpleAppState; -import jme3utilities.math.MyArray; -import jme3utilities.math.MyMath; -import jme3utilities.ui.AcorusDemo; - -/** - * AppState to display the status of the DropTest application in an overlay. The - * overlay consists of status lines, one of which is selected for editing. The - * overlay is located in the upper-left portion of the display. - * - * @author Stephen Gold sgold@sonic.net - */ -class DropTestStatus extends SimpleAppState { - // ************************************************************************* - // constants and loggers - - /** - * list of damping fractions, in ascending order - */ - final private static float[] dampingValues - = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; - /** - * list of friction coefficients, in ascending order - */ - final private static float[] frictionValues - = {0f, 0.1f, 0.2f, 0.5f, 1f, 2f, 4f}; - /** - * list of gravity magnitudes, in ascending order - */ - final private static float[] gravityValues - = {1f, 2f, 5f, 10f, 20f, 30f, 50f}; - /** - * list of restitution fractions, in ascending order - */ - final private static float[] restitutionValues - = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; - /** - * index of the status line for the damping fraction - */ - final private static int dampingStatusLine = 4; - /** - * index of the status line for the type of the next drop - */ - final private static int dropStatusLine = 3; - /** - * index of the status line for the friction coefficient - */ - final private static int frictionStatusLine = 5; - /** - * index of the status line for the gravity magnitude - */ - final private static int gravityStatusLine = 6; - /** - * number of lines of text in the overlay - */ - final private static int numStatusLines = 8; - /** - * index of the status line for the platform name - */ - final private static int platformStatusLine = 2; - /** - * index of the status line for the restitution fraction - */ - final private static int restitutionStatusLine = 7; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(DropTestStatus.class.getName()); - /** - * names of all drop types, in ascending lexicographic order - */ - final private static String[] dropNames = { - "ankh", "banana", "barbell", "barrel", "bowl", "bowlingPin", "box", - "breakableRod", "capsule", "chain", "chair", "cloth", "cone", "coneBox", - "cylinder", "cylinderBox", "digit", "diptych", "dome", "duck", "flail", - "football", "frame", "halfPipe", "heart", "horseshoe", "hull", "iBeam", - "knucklebone", "ladder", "letter", "lidlessBox", "link", "madMallet", - "mallet", "multiSphere", "platonic", "prism", "pyramid", "ragdoll", - "roundedDisc", "saucer", "snowman", "sphere", "squishyBall", "star", - "sword", "table", "teapot", "teapotGi", "tetrahedron", "thumbTack", - "top", "torus", "triangularFrame", "trident", "washer" - }; - /** - * list of platform names, in ascending lexicographic order - */ - final private static String[] platformNames = { - "bedOfNails", "box", "candyDish", "cone", "corner", "cylinder", - "dimples", "hull", "plane", "roundedRectangle", "sieve", "smooth", - "square", "trampoline", "tray", "triangle" - }; - // ************************************************************************* - // fields - - /** - * lines of text displayed in the upper-left corner of the GUI node ([0] is - * the top line) - */ - final private BitmapText[] statusLines = new BitmapText[numStatusLines]; - /** - * flag to enable child coloring for unselected PCOs with compound shapes - */ - private boolean isChildColoring = false; - /** - * flag to force wireframe materials for all unselected PCOs (overrides - * child coloring) - */ - private boolean isWireframe = false; - /** - * reference to the application instance - */ - private DropTest appInstance; - /** - * damping fraction for all dynamic bodies (≥0, ≤1) - */ - private float damping = 0.6f; - /** - * friction coefficient for all rigid bodies (≥0) - */ - private float friction = 0.5f; - /** - * gravity magnitude for all dynamic bodies (in physics-space units per - * second squared, ≥0) - */ - private float gravity = 30f; - /** - * restitution all all rigid bodies (≥0, ≤1) - */ - private float restitution = 0.3f; - /** - * index of the line being edited (≥1) - */ - private int selectedLine = dropStatusLine; - /** - * name of the type selected for the next drop - */ - private String nextDropType = "multiSphere"; - /** - * name of the platform - */ - private String platformName = "box"; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized enabled state. - */ - DropTestStatus() { - super(true); - } - // ************************************************************************* - // new methods exposed - - /** - * Advance the field selection by the specified amount. - * - * @param amount the number of fields to move downward - */ - void advanceSelectedField(int amount) { - int firstField = 2; - int numFields = numStatusLines - firstField; - - int selectedField = selectedLine - firstField; - int sum = selectedField + amount; - selectedField = MyMath.modulo(sum, numFields); - this.selectedLine = selectedField + firstField; - } - - /** - * Advance the value of the selected field by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - void advanceValue(int amount) { - switch (selectedLine) { - case dampingStatusLine: - advanceDamping(amount); - break; - - case dropStatusLine: - advanceDrop(amount); - break; - - case frictionStatusLine: - advanceFriction(amount); - break; - - case gravityStatusLine: - advanceGravity(amount); - break; - - case platformStatusLine: - advancePlatform(amount); - break; - - case restitutionStatusLine: - advanceRestitution(amount); - break; - - default: - throw new IllegalStateException("line = " + selectedLine); - } - } - - /** - * Determine the selected damping fraction for all dynamic bodies. - * - * @return the fraction (≥0, ≤1) - */ - float damping() { - assert damping >= 0f : damping; - assert damping <= 1f : damping; - return damping; - } - - /** - * Determine the selected friction coefficient for all rigid bodies. - * - * @return the coefficient (≥0) - */ - float friction() { - assert friction >= 0f : friction; - return friction; - } - - /** - * Determine the selected gravity magnitude for all dynamic bodies. - * - * @return the acceleration (in world units per second squared, ≥0) - */ - float gravity() { - assert gravity >= 0f : gravity; - return gravity; - } - - /** - * Test whether child coloring is enabled. - * - * @return true if enabled, otherwise false - */ - boolean isChildColoring() { - return isChildColoring; - } - - /** - * Test whether wireframe materials are enabled. - * - * @return true if enabled, otherwise false - */ - boolean isWireframe() { - return isWireframe; - } - - /** - * Determine the selected type for the next drop. - * - * @return the name (not null, not empty) - */ - String nextDropType() { - assert nextDropType != null; - assert !nextDropType.isEmpty(); - return nextDropType; - } - - /** - * Determine the selected type of platform. - * - * @return the name (not null, not empty) - */ - String platformType() { - assert platformName != null; - assert !platformName.isEmpty(); - return platformName; - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - void resize(int newWidth, int newHeight) { - if (isInitialized()) { - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - } - } - - /** - * Determine the selected restitution fraction for all rigid bodies. - * - * @return the fraction (≥0, ≤1) - */ - float restitution() { - assert restitution >= 0f : restitution; - assert restitution <= 1f : restitution; - return restitution; - } - - /** - * Toggle child coloring disabled/enabled. - */ - void toggleChildColor() { - this.isChildColoring = !isChildColoring; - } - - /** - * Toggle wireframe disabled/enabled. - */ - void toggleWireframe() { - this.isWireframe = !isWireframe; - } - // ************************************************************************* - // ActionAppState methods - - /** - * Clean up this AppState during the first update after it gets detached. - * Should be invoked only by a subclass or by the AppStateManager. - */ - @Override - public void cleanup() { - super.cleanup(); - - // Remove the status lines from the guiNode. - for (int i = 0; i < numStatusLines; ++i) { - statusLines[i].removeFromParent(); - } - } - - /** - * Initialize this AppState on the first update after it gets attached. - * - * @param sm application's state manager (not null) - * @param app application which owns this state (not null) - */ - @Override - public void initialize(AppStateManager sm, Application app) { - super.initialize(sm, app); - - this.appInstance = (DropTest) app; - BitmapFont guiFont - = assetManager.loadFont("Interface/Fonts/Default.fnt"); - - // Add status lines to the guiNode. - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - float y = cam.getHeight() - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - guiNode.attachChild(statusLines[lineIndex]); - } - - assert MyArray.isSorted(dampingValues); - assert MyArray.isSorted(frictionValues); - assert MyArray.isSorted(gravityValues); - assert MyArray.isSorted(restitutionValues); - - assert MyArray.isSorted(dropNames); - assert MyArray.isSorted(platformNames); - } - - /** - * Callback to update this AppState prior to rendering. (Invoked once per - * frame while the state is attached and enabled.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - updateStatusText(); - - int index = 1 + Arrays.binarySearch(dampingValues, damping); - int count = dampingValues.length; - String message = String.format( - "Damping #%d of %d: %.2f", index, count, damping); - updateStatusLine(dampingStatusLine, message); - - index = 1 + Arrays.binarySearch(frictionValues, friction); - count = frictionValues.length; - message = String.format( - "Friction #%d of %d: %.1f", index, count, friction); - updateStatusLine(frictionStatusLine, message); - - index = 1 + Arrays.binarySearch(gravityValues, gravity); - count = gravityValues.length; - message = String.format( - "Gravity #%d of %d: %.1f", index, count, gravity); - updateStatusLine(gravityStatusLine, message); - - index = 1 + Arrays.binarySearch(dropNames, nextDropType); - count = dropNames.length; - message = String.format( - "Drop #%d of %d: %s", index, count, nextDropType); - updateStatusLine(dropStatusLine, message); - - index = 1 + Arrays.binarySearch(platformNames, platformName); - count = platformNames.length; - message = String.format( - "Platform #%d of %d: %s", index, count, platformName); - updateStatusLine(platformStatusLine, message); - - index = 1 + Arrays.binarySearch(restitutionValues, restitution); - count = restitutionValues.length; - message = String.format( - "Restitution #%d of %d: %.2f", index, count, restitution); - updateStatusLine(restitutionStatusLine, message); - } - // ************************************************************************* - // private methods - - /** - * Advance the damping selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceDamping(int amount) { - this.damping = AcorusDemo.advanceFloat(dampingValues, damping, amount); - appInstance.setDampingAll(damping); - } - - /** - * Advance the next-drop selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceDrop(int amount) { - this.nextDropType - = AcorusDemo.advanceString(dropNames, nextDropType, amount); - } - - /** - * Advance the friction selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceFriction(int amount) { - this.friction - = AcorusDemo.advanceFloat(frictionValues, friction, amount); - appInstance.setFrictionAll(friction); - } - - /** - * Advance the gravity selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceGravity(int amount) { - this.gravity = AcorusDemo.advanceFloat(gravityValues, gravity, amount); - appInstance.setGravityAll(gravity); - } - - /** - * Advance the platform selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advancePlatform(int amount) { - this.platformName - = AcorusDemo.advanceString(platformNames, platformName, amount); - appInstance.restartScenario(); - } - - /** - * Advance the restitution selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceRestitution(int amount) { - this.restitution = AcorusDemo.advanceFloat( - restitutionValues, restitution, amount); - appInstance.setRestitutionAll(restitution); - } - - /** - * Update the indexed status line. - * - * @param lineIndex which status line (≥0) - * @param text the text to display, not including the arrow, if any - */ - private void updateStatusLine(int lineIndex, String text) { - BitmapText spatial = statusLines[lineIndex]; - - if (lineIndex == selectedLine) { - spatial.setColor(ColorRGBA.Yellow); - spatial.setText("-> " + text); - } else { - spatial.setColor(ColorRGBA.White); - spatial.setText(" " + text); - } - } - - /** - * Update the status text (top 2 lines). - */ - private void updateStatusText() { - String message = " View: "; - if (isWireframe) { - message += "Wireframe "; - } else if (isChildColoring) { - message += "Lit/ChildColored "; - } else { - message += "Lit "; - } - String viewOptions = appInstance.describePhysicsDebugOptions(); - message += viewOptions; - statusLines[0].setText(message); - - int numDrops = DropTest.countDrops(); - int numActiveBodies = appInstance.countActive(); - int numCachedMeshes = DebugShapeFactory.countCachedMeshes(); - boolean isPaused = appInstance.isPaused(); - message = String.format(" drops=%d activeBodies=%d cachedMeshes=%d%s", - numDrops, numActiveBodies, numCachedMeshes, - isPaused ? " PAUSED" : ""); - statusLines[1].setText(message); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.math.ColorRGBA; +import java.util.Arrays; +import java.util.logging.Logger; +import jme3utilities.SimpleAppState; +import jme3utilities.math.MyArray; +import jme3utilities.math.MyMath; +import jme3utilities.ui.AcorusDemo; + +/** + * AppState to display the status of the DropTest application in an overlay. The + * overlay consists of status lines, one of which is selected for editing. The + * overlay is located in the upper-left portion of the display. + * + * @author Stephen Gold sgold@sonic.net + */ +class DropTestStatus extends SimpleAppState { + // ************************************************************************* + // constants and loggers + + /** + * list of damping fractions, in ascending order + */ + final private static float[] dampingValues + = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; + /** + * list of friction coefficients, in ascending order + */ + final private static float[] frictionValues + = {0f, 0.1f, 0.2f, 0.5f, 1f, 2f, 4f}; + /** + * list of gravity magnitudes, in ascending order + */ + final private static float[] gravityValues + = {1f, 2f, 5f, 10f, 20f, 30f, 50f}; + /** + * list of restitution fractions, in ascending order + */ + final private static float[] restitutionValues + = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; + /** + * index of the status line for the damping fraction + */ + final private static int dampingStatusLine = 4; + /** + * index of the status line for the type of the next drop + */ + final private static int dropStatusLine = 3; + /** + * index of the status line for the friction coefficient + */ + final private static int frictionStatusLine = 5; + /** + * index of the status line for the gravity magnitude + */ + final private static int gravityStatusLine = 6; + /** + * number of lines of text in the overlay + */ + final private static int numStatusLines = 8; + /** + * index of the status line for the platform name + */ + final private static int platformStatusLine = 2; + /** + * index of the status line for the restitution fraction + */ + final private static int restitutionStatusLine = 7; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(DropTestStatus.class.getName()); + /** + * names of all drop types, in ascending lexicographic order + */ + final private static String[] dropNames = { + "ankh", "banana", "barbell", "barrel", "bowl", "bowlingPin", "box", + "breakableRod", "capsule", "chain", "chair", "cloth", "cone", "coneBox", + "cylinder", "cylinderBox", "digit", "diptych", "dome", "duck", "flail", + "football", "frame", "halfPipe", "heart", "horseshoe", "hull", "iBeam", + "knucklebone", "ladder", "letter", "lidlessBox", "link", "madMallet", + "mallet", "multiSphere", "platonic", "prism", "pyramid", "ragdoll", + "roundedDisc", "saucer", "snowman", "sphere", "squishyBall", "star", + "sword", "table", "teapot", "teapotGi", "tetrahedron", "thumbTack", + "top", "torus", "triangularFrame", "trident", "washer" + }; + /** + * list of platform names, in ascending lexicographic order + */ + final private static String[] platformNames = { + "bedOfNails", "box", "candyDish", "cone", "corner", "cylinder", + "dimples", "hull", "plane", "roundedRectangle", "sieve", "smooth", + "square", "trampoline", "tray", "triangle" + }; + // ************************************************************************* + // fields + + /** + * lines of text displayed in the upper-left corner of the GUI node ([0] is + * the top line) + */ + final private BitmapText[] statusLines = new BitmapText[numStatusLines]; + /** + * flag to enable child coloring for unselected PCOs with compound shapes + */ + private boolean isChildColoring = false; + /** + * flag to force wireframe materials for all unselected PCOs (overrides + * child coloring) + */ + private boolean isWireframe = false; + /** + * reference to the application instance + */ + private DropTest appInstance; + /** + * damping fraction for all dynamic bodies (≥0, ≤1) + */ + private float damping = 0.6f; + /** + * friction coefficient for all rigid bodies (≥0) + */ + private float friction = 0.5f; + /** + * gravity magnitude for all dynamic bodies (in physics-space units per + * second squared, ≥0) + */ + private float gravity = 30f; + /** + * restitution all all rigid bodies (≥0, ≤1) + */ + private float restitution = 0.3f; + /** + * index of the line being edited (≥1) + */ + private int selectedLine = dropStatusLine; + /** + * name of the type selected for the next drop + */ + private String nextDropType = "multiSphere"; + /** + * name of the platform + */ + private String platformName = "box"; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized enabled state. + */ + DropTestStatus() { + super(true); + } + // ************************************************************************* + // new methods exposed + + /** + * Advance the field selection by the specified amount. + * + * @param amount the number of fields to move downward + */ + void advanceSelectedField(int amount) { + int firstField = 2; + int numFields = numStatusLines - firstField; + + int selectedField = selectedLine - firstField; + int sum = selectedField + amount; + selectedField = MyMath.modulo(sum, numFields); + this.selectedLine = selectedField + firstField; + } + + /** + * Advance the value of the selected field by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + void advanceValue(int amount) { + switch (selectedLine) { + case dampingStatusLine: + advanceDamping(amount); + break; + + case dropStatusLine: + advanceDrop(amount); + break; + + case frictionStatusLine: + advanceFriction(amount); + break; + + case gravityStatusLine: + advanceGravity(amount); + break; + + case platformStatusLine: + advancePlatform(amount); + break; + + case restitutionStatusLine: + advanceRestitution(amount); + break; + + default: + throw new IllegalStateException("line = " + selectedLine); + } + } + + /** + * Determine the selected damping fraction for all dynamic bodies. + * + * @return the fraction (≥0, ≤1) + */ + float damping() { + assert damping >= 0f : damping; + assert damping <= 1f : damping; + return damping; + } + + /** + * Determine the selected friction coefficient for all rigid bodies. + * + * @return the coefficient (≥0) + */ + float friction() { + assert friction >= 0f : friction; + return friction; + } + + /** + * Determine the selected gravity magnitude for all dynamic bodies. + * + * @return the acceleration (in world units per second squared, ≥0) + */ + float gravity() { + assert gravity >= 0f : gravity; + return gravity; + } + + /** + * Test whether child coloring is enabled. + * + * @return true if enabled, otherwise false + */ + boolean isChildColoring() { + return isChildColoring; + } + + /** + * Test whether wireframe materials are enabled. + * + * @return true if enabled, otherwise false + */ + boolean isWireframe() { + return isWireframe; + } + + /** + * Determine the selected type for the next drop. + * + * @return the name (not null, not empty) + */ + String nextDropType() { + assert nextDropType != null; + assert !nextDropType.isEmpty(); + return nextDropType; + } + + /** + * Determine the selected type of platform. + * + * @return the name (not null, not empty) + */ + String platformType() { + assert platformName != null; + assert !platformName.isEmpty(); + return platformName; + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + void resize(int newWidth, int newHeight) { + if (isInitialized()) { + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + } + } + + /** + * Determine the selected restitution fraction for all rigid bodies. + * + * @return the fraction (≥0, ≤1) + */ + float restitution() { + assert restitution >= 0f : restitution; + assert restitution <= 1f : restitution; + return restitution; + } + + /** + * Toggle child coloring disabled/enabled. + */ + void toggleChildColor() { + this.isChildColoring = !isChildColoring; + } + + /** + * Toggle wireframe disabled/enabled. + */ + void toggleWireframe() { + this.isWireframe = !isWireframe; + } + // ************************************************************************* + // ActionAppState methods + + /** + * Clean up this AppState during the first update after it gets detached. + * Should be invoked only by a subclass or by the AppStateManager. + */ + @Override + public void cleanup() { + super.cleanup(); + + // Remove the status lines from the guiNode. + for (int i = 0; i < numStatusLines; ++i) { + statusLines[i].removeFromParent(); + } + } + + /** + * Initialize this AppState on the first update after it gets attached. + * + * @param sm application's state manager (not null) + * @param app application which owns this state (not null) + */ + @Override + public void initialize(AppStateManager sm, Application app) { + super.initialize(sm, app); + + this.appInstance = (DropTest) app; + BitmapFont guiFont + = assetManager.loadFont("Interface/Fonts/Default.fnt"); + + // Add status lines to the guiNode. + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + float y = cam.getHeight() - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + guiNode.attachChild(statusLines[lineIndex]); + } + + assert MyArray.isSorted(dampingValues); + assert MyArray.isSorted(frictionValues); + assert MyArray.isSorted(gravityValues); + assert MyArray.isSorted(restitutionValues); + + assert MyArray.isSorted(dropNames); + assert MyArray.isSorted(platformNames); + } + + /** + * Callback to update this AppState prior to rendering. (Invoked once per + * frame while the state is attached and enabled.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + updateStatusText(); + + int index = 1 + Arrays.binarySearch(dampingValues, damping); + int count = dampingValues.length; + String message = String.format( + "Damping #%d of %d: %.2f", index, count, damping); + updateStatusLine(dampingStatusLine, message); + + index = 1 + Arrays.binarySearch(frictionValues, friction); + count = frictionValues.length; + message = String.format( + "Friction #%d of %d: %.1f", index, count, friction); + updateStatusLine(frictionStatusLine, message); + + index = 1 + Arrays.binarySearch(gravityValues, gravity); + count = gravityValues.length; + message = String.format( + "Gravity #%d of %d: %.1f", index, count, gravity); + updateStatusLine(gravityStatusLine, message); + + index = 1 + Arrays.binarySearch(dropNames, nextDropType); + count = dropNames.length; + message = String.format( + "Drop #%d of %d: %s", index, count, nextDropType); + updateStatusLine(dropStatusLine, message); + + index = 1 + Arrays.binarySearch(platformNames, platformName); + count = platformNames.length; + message = String.format( + "Platform #%d of %d: %s", index, count, platformName); + updateStatusLine(platformStatusLine, message); + + index = 1 + Arrays.binarySearch(restitutionValues, restitution); + count = restitutionValues.length; + message = String.format( + "Restitution #%d of %d: %.2f", index, count, restitution); + updateStatusLine(restitutionStatusLine, message); + } + // ************************************************************************* + // private methods + + /** + * Advance the damping selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceDamping(int amount) { + this.damping = AcorusDemo.advanceFloat(dampingValues, damping, amount); + appInstance.setDampingAll(damping); + } + + /** + * Advance the next-drop selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceDrop(int amount) { + this.nextDropType + = AcorusDemo.advanceString(dropNames, nextDropType, amount); + } + + /** + * Advance the friction selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceFriction(int amount) { + this.friction + = AcorusDemo.advanceFloat(frictionValues, friction, amount); + appInstance.setFrictionAll(friction); + } + + /** + * Advance the gravity selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceGravity(int amount) { + this.gravity = AcorusDemo.advanceFloat(gravityValues, gravity, amount); + appInstance.setGravityAll(gravity); + } + + /** + * Advance the platform selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advancePlatform(int amount) { + this.platformName + = AcorusDemo.advanceString(platformNames, platformName, amount); + appInstance.restartScenario(); + } + + /** + * Advance the restitution selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceRestitution(int amount) { + this.restitution = AcorusDemo.advanceFloat( + restitutionValues, restitution, amount); + appInstance.setRestitutionAll(restitution); + } + + /** + * Update the indexed status line. + * + * @param lineIndex which status line (≥0) + * @param text the text to display, not including the arrow, if any + */ + private void updateStatusLine(int lineIndex, String text) { + BitmapText spatial = statusLines[lineIndex]; + + if (lineIndex == selectedLine) { + spatial.setColor(ColorRGBA.Yellow); + spatial.setText("-> " + text); + } else { + spatial.setColor(ColorRGBA.White); + spatial.setText(" " + text); + } + } + + /** + * Update the status text (top 2 lines). + */ + private void updateStatusText() { + String message = " View: "; + if (isWireframe) { + message += "Wireframe "; + } else if (isChildColoring) { + message += "Lit/ChildColored "; + } else { + message += "Lit "; + } + String viewOptions = appInstance.describePhysicsDebugOptions(); + message += viewOptions; + statusLines[0].setText(message); + + int numDrops = DropTest.countDrops(); + int numActiveBodies = appInstance.countActive(); + int numCachedMeshes = DebugShapeFactory.countCachedMeshes(); + boolean isPaused = appInstance.isPaused(); + message = String.format(" drops=%d activeBodies=%d cachedMeshes=%d%s", + numDrops, numActiveBodies, numCachedMeshes, + isPaused ? " PAUSED" : ""); + statusLines[1].setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/ForceDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/ForceDemo.java index 55aaa4662..8584987ce 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/ForceDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/ForceDemo.java @@ -1,329 +1,329 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; - -/** - * Test/demonstrate rigid-body forces, torques, and impulses. - *

- * Collision objects are rendered entirely by debug visualization. - * - * @author Stephen Gold sgold@sonic.net - */ -public class ForceDemo - extends PhysicsDemo - implements PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(ForceDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = ForceDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed in the upper-left corner of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * subject body to which forces and torques are applied - */ - private static PhysicsRigidBody cube; - // ************************************************************************* - // constructors - - /** - * Instantiate the ForceDemo application. - */ - public ForceDemo() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the ForceDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new ForceDemo(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - guiNode.attachChild(statusText); - - super.acorusInit(); - - configureCamera(); - configureDumper(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - float length = 0.8f; - attachWorldAxes(length); - - // Add a spinning cube. - BoxCollisionShape shape = new BoxCollisionShape(1f); - cube = new PhysicsRigidBody(shape); - cube.setEnableSleep(false); - Quaternion initialOrientation - = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); - cube.setPhysicsRotation(initialOrientation); - addCollisionObject(cube); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpScene, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - - dim.bindSignal("cf+Y", KeyInput.KEY_F3); - dim.bindSignal("cf-Y", KeyInput.KEY_F4); - dim.bindSignal("ci+Y", KeyInput.KEY_F7); - dim.bindSignal("ci-Y", KeyInput.KEY_F8); - dim.bindSignal("for+Y@+X", KeyInput.KEY_F6); - dim.bindSignal("imp+Y@+X", KeyInput.KEY_F9); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - dim.bindSignal("torque+Y", KeyInput.KEY_F1); - dim.bindSignal("torque-Y", KeyInput.KEY_F2); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - dim.bind(asToggleWArrows, KeyInput.KEY_N); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - statusText.setLocalTranslation(0f, newHeight, 0f); - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusText(); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before the physics is stepped. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Check UI signals and apply forces/torques accordingly. - Signals signals = getSignals(); - - if (signals.test("cf+Y")) { - cube.applyCentralForce(new Vector3f(0f, 1f, 0f)); - } - if (signals.test("cf-Y")) { - cube.applyCentralForce(new Vector3f(0f, -1f, 0f)); - } - if (signals.test("ci+Y")) { - cube.applyCentralImpulse(new Vector3f(0f, 0.1f, 0f)); - } - if (signals.test("ci-Y")) { - cube.applyCentralImpulse(new Vector3f(0f, -0.1f, 0f)); - } - if (signals.test("for+Y@+X")) { - cube.applyForce( - new Vector3f(0f, 1f, 0f), new Vector3f(1f, 0f, 0f)); - } - if (signals.test("imp+Y@+X")) { - cube.applyImpulse( - new Vector3f(0f, 0.1f, 0f), new Vector3f(1f, 0f, 0f)); - } - if (signals.test("torque+Y")) { - cube.applyTorque(new Vector3f(0f, 1f, 0f)); - } - if (signals.test("torque-Y")) { - cube.applyTorque(new Vector3f(0f, -1f, 0f)); - } - } - - /** - * Callback from Bullet, invoked just after the physics has been stepped. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(4f); - flyCam.setZoomSpeed(4f); - - cam.setLocation(new Vector3f(2.65f, 2.42f, 9.37f)); - cam.setRotation(new Quaternion(0f, 0.9759f, -0.04f, -0.2136f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - float axisLength = maxArrowLength(); - bulletAppState.setDebugAxisLength(axisLength); - bulletAppState.setDebugEnabled(true); - stateManager.attach(bulletAppState); - - PhysicsSpace space = getPhysicsSpace(); - space.addTickListener(this); - setGravityAll(0f); - } - - /** - * Update the status text in the GUI. - */ - private static void updateStatusText() { - float v = cube.getLinearVelocity().length(); - float omega = cube.getAngularVelocity().length(); - String message = String.format(" v=%f psu/s, omega=%f rad/s", v, omega); - statusText.setText(message); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; + +/** + * Test/demonstrate rigid-body forces, torques, and impulses. + *

+ * Collision objects are rendered entirely by debug visualization. + * + * @author Stephen Gold sgold@sonic.net + */ +public class ForceDemo + extends PhysicsDemo + implements PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(ForceDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = ForceDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed in the upper-left corner of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * subject body to which forces and torques are applied + */ + private static PhysicsRigidBody cube; + // ************************************************************************* + // constructors + + /** + * Instantiate the ForceDemo application. + */ + public ForceDemo() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the ForceDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new ForceDemo(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + guiNode.attachChild(statusText); + + super.acorusInit(); + + configureCamera(); + configureDumper(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + float length = 0.8f; + attachWorldAxes(length); + + // Add a spinning cube. + BoxCollisionShape shape = new BoxCollisionShape(1f); + cube = new PhysicsRigidBody(shape); + cube.setEnableSleep(false); + Quaternion initialOrientation + = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); + cube.setPhysicsRotation(initialOrientation); + addCollisionObject(cube); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpScene, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + + dim.bindSignal("cf+Y", KeyInput.KEY_F3); + dim.bindSignal("cf-Y", KeyInput.KEY_F4); + dim.bindSignal("ci+Y", KeyInput.KEY_F7); + dim.bindSignal("ci-Y", KeyInput.KEY_F8); + dim.bindSignal("for+Y@+X", KeyInput.KEY_F6); + dim.bindSignal("imp+Y@+X", KeyInput.KEY_F9); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + dim.bindSignal("torque+Y", KeyInput.KEY_F1); + dim.bindSignal("torque-Y", KeyInput.KEY_F2); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + dim.bind(asToggleWArrows, KeyInput.KEY_N); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + statusText.setLocalTranslation(0f, newHeight, 0f); + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusText(); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before the physics is stepped. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Check UI signals and apply forces/torques accordingly. + Signals signals = getSignals(); + + if (signals.test("cf+Y")) { + cube.applyCentralForce(new Vector3f(0f, 1f, 0f)); + } + if (signals.test("cf-Y")) { + cube.applyCentralForce(new Vector3f(0f, -1f, 0f)); + } + if (signals.test("ci+Y")) { + cube.applyCentralImpulse(new Vector3f(0f, 0.1f, 0f)); + } + if (signals.test("ci-Y")) { + cube.applyCentralImpulse(new Vector3f(0f, -0.1f, 0f)); + } + if (signals.test("for+Y@+X")) { + cube.applyForce( + new Vector3f(0f, 1f, 0f), new Vector3f(1f, 0f, 0f)); + } + if (signals.test("imp+Y@+X")) { + cube.applyImpulse( + new Vector3f(0f, 0.1f, 0f), new Vector3f(1f, 0f, 0f)); + } + if (signals.test("torque+Y")) { + cube.applyTorque(new Vector3f(0f, 1f, 0f)); + } + if (signals.test("torque-Y")) { + cube.applyTorque(new Vector3f(0f, -1f, 0f)); + } + } + + /** + * Callback from Bullet, invoked just after the physics has been stepped. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(4f); + flyCam.setZoomSpeed(4f); + + cam.setLocation(new Vector3f(2.65f, 2.42f, 9.37f)); + cam.setRotation(new Quaternion(0f, 0.9759f, -0.04f, -0.2136f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + float axisLength = maxArrowLength(); + bulletAppState.setDebugAxisLength(axisLength); + bulletAppState.setDebugEnabled(true); + stateManager.attach(bulletAppState); + + PhysicsSpace space = getPhysicsSpace(); + space.addTickListener(this); + setGravityAll(0f); + } + + /** + * Update the status text in the GUI. + */ + private static void updateStatusText() { + float v = cube.getLinearVelocity().length(); + float omega = cube.getAngularVelocity().length(); + String message = String.format(" v=%f psu/s, omega=%f rad/s", v, omega); + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/JointDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/JointDemo.java index 0b4a6eb5e..bc019fea0 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/JointDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/JointDemo.java @@ -1,494 +1,494 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyAsset; -import jme3utilities.MyString; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; - -/** - * Test/demonstrate double-ended New6Dof joints. - * - * @author Stephen Gold sgold@sonic.net - */ -public class JointDemo extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(JointDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = JointDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed in the upper-left corner of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * scene-graph node for visualizing solid objects - */ - final private static Node meshesNode = new Node("meshes node"); - /** - * motor to rotate the left-front leg - */ - private static RotationMotor lfMotor; - /** - * motor to rotate the left-rear leg - */ - private static RotationMotor lrMotor; - /** - * motor to rotate the right-front leg - */ - private static RotationMotor rfMotor; - /** - * motor to rotate the right-rear leg - */ - private static RotationMotor rrMotor; - // ************************************************************************* - // constructors - - /** - * Instantiate the JointDemo application. - */ - public JointDemo() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the JointDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new JointDemo(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - addLighting(); - - float length = 0.8f; - attachWorldAxes(length); - - float halfExtent = 50f; - float topY = 0f; - attachCubePlatform(halfExtent, topY); - - addRobot(); - - rootNode.attachChild(meshesNode); - meshesNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - statusText.setLocalTranslation(0f, cam.getHeight(), 0f); - guiNode.attachChild(statusText); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - super.configureDumper(); - - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.JointsInSpaces, true); - dumper.setEnabled(DumpFlags.Motors, true); - } - - /** - * Generate materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - ColorRGBA brown = new ColorRGBA().setAsSrgb(0.3f, 0.3f, 0.1f, 1f); - Material robotMaterial - = MyAsset.createShadedMaterial(assetManager, brown); - registerMaterial("robot", robotMaterial); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 0.5f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpScene, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - dim.bind("signal turnLF", KeyInput.KEY_NUMPAD7); - dim.bind("signal turnRF", KeyInput.KEY_NUMPAD9); - dim.bind("signal turnLR", KeyInput.KEY_NUMPAD1); - dim.bind("signal turnRR", KeyInput.KEY_NUMPAD3); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind("toggle view", KeyInput.KEY_SLASH); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "toggle view": - toggleMeshes(); - togglePhysicsDebug(); - return; - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - // Check UI signals and update motor velocities accordingly. - Signals signals = getSignals(); - - float lfVelocity = signals.test("turnLF") ? 2f : 0f; - lfMotor.set(MotorParam.TargetVelocity, lfVelocity); - - float rfVelocity = signals.test("turnRF") ? 2f : 0f; - rfMotor.set(MotorParam.TargetVelocity, rfVelocity); - - float lrVelocity = signals.test("turnLR") ? 2f : 0f; - lrMotor.set(MotorParam.TargetVelocity, lrVelocity); - - float rrVelocity = signals.test("turnRR") ? 2f : 0f; - rrMotor.set(MotorParam.TargetVelocity, rrVelocity); - - updateStatusText(); - } - // ************************************************************************* - // private methods - - /** - * Add a rectangular leg to the robot chassis. - * - * @param legGeom the geometry for the leg (not null) - * @param legInWorld the location of the leg (in world coordinates, not - * null) - * @param pivotInChassis the pivot offset relative to the chassis (not null) - * @param chassisInWorld the chassis location (in world coordinates, not - * null) - * @param chassisRbc the body for the chassis (not null) - * @return the new instance - */ - private RotationMotor addLeg(Geometry legGeom, Vector3f legInWorld, - Vector3f pivotInChassis, Vector3f chassisInWorld, - RigidBodyControl chassisRbc) { - meshesNode.attachChild(legGeom); - legGeom.move(legInWorld); - Material robotMaterial = findMaterial("robot"); - legGeom.setMaterial(robotMaterial); - - CollisionShape shape = new BoxCollisionShape(0.1f, 0.4f, 0.1f); - float mass = 0.1f; - RigidBodyControl legRbc = new RigidBodyControl(shape, mass); - legGeom.addControl(legRbc); - PhysicsSpace physicsSpace = getPhysicsSpace(); - legRbc.setPhysicsSpace(physicsSpace); - legRbc.setEnableSleep(false); - - Vector3f pivotInLeg - = pivotInChassis.add(chassisInWorld).subtractLocal(legInWorld); - New6Dof joint = new New6Dof(chassisRbc, legRbc, pivotInChassis, - pivotInLeg, new Matrix3f(), new Matrix3f(), RotationOrder.ZYX); - - // Inhibit X- and Y-axis rotations. - RotationMotor xMotor = joint.getRotationMotor(PhysicsSpace.AXIS_X); - xMotor.set(MotorParam.UpperLimit, 0f); - xMotor.set(MotorParam.LowerLimit, 0f); - RotationMotor yMotor = joint.getRotationMotor(PhysicsSpace.AXIS_Y); - yMotor.set(MotorParam.UpperLimit, 0f); - yMotor.set(MotorParam.LowerLimit, 0f); - - // Enable the motor for Z-axis rotation and return a reference to it. - RotationMotor zMotor = joint.getRotationMotor(PhysicsSpace.AXIS_Z); - zMotor.setMotorEnabled(true); - zMotor.set(MotorParam.MaxMotorForce, 9e9f); - - physicsSpace.addJoint(joint); - - return zMotor; - } - - /** - * Add lighting and shadows to the main scene. - */ - private void addLighting() { - ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootNode.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - sun.setName("sun"); - - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.5f); - viewPort.addProcessor(dlsr); - } - - /** - * Add a 4-legged robot to the scene. - */ - private void addRobot() { - Mesh chassisMesh = new Box(1f, 0.3f, 0.3f); - CollisionShape chassisShape = new BoxCollisionShape(1f, 0.3f, 0.3f); - Vector3f chassisInWorld = new Vector3f(0f, 1f, 0f); - - Geometry chassisGeom = new Geometry("chassis", chassisMesh); - meshesNode.attachChild(chassisGeom); - chassisGeom.move(chassisInWorld); - Material robotMaterial = findMaterial("robot"); - chassisGeom.setMaterial(robotMaterial); - chassisGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - - float chassisMass = 1f; - RigidBodyControl chassisRbc - = new RigidBodyControl(chassisShape, chassisMass); - chassisGeom.addControl(chassisRbc); - PhysicsSpace physicsSpace = getPhysicsSpace(); - chassisRbc.setPhysicsSpace(physicsSpace); - chassisRbc.setEnableSleep(false); - - Mesh legMesh = new Box(0.1f, 0.4f, 0.1f); - - Geometry lfGeom = new Geometry("lf leg", legMesh); - Vector3f lfLegInWorld = new Vector3f(-0.8f, 0.6f, 0.5f); - Vector3f lfPivotInChassis = new Vector3f(-0.8f, 0f, 0.4f); - lfMotor = addLeg(lfGeom, lfLegInWorld, lfPivotInChassis, - chassisInWorld, chassisRbc); - - Geometry rfGeom = new Geometry("rf leg", legMesh); - Vector3f rfLegInWorld = new Vector3f(-0.8f, 0.6f, -0.5f); - Vector3f rfPivotInChassis = new Vector3f(-0.8f, 0f, -0.4f); - rfMotor = addLeg(rfGeom, rfLegInWorld, rfPivotInChassis, - chassisInWorld, chassisRbc); - - Geometry lrGeom = new Geometry("lr leg", legMesh); - Vector3f lrLegInWorld = new Vector3f(0.8f, 0.6f, 0.5f); - Vector3f lrPivotInChassis = new Vector3f(0.8f, 0f, 0.4f); - lrMotor = addLeg(lrGeom, lrLegInWorld, lrPivotInChassis, - chassisInWorld, chassisRbc); - - Geometry rrGeom = new Geometry("rr leg", legMesh); - Vector3f rrLegInWorld = new Vector3f(0.8f, 0.6f, -0.5f); - Vector3f rrPivotInChassis = new Vector3f(0.8f, 0f, -0.4f); - rrMotor = addLeg(rrGeom, rrLegInWorld, rrPivotInChassis, - chassisInWorld, chassisRbc); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(4f); - flyCam.setZoomSpeed(4f); - - cam.setLocation(new Vector3f(2.65f, 2.42f, 9.37f)); - cam.setRotation(new Quaternion(0f, 0.9759f, -0.04f, -0.2136f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - } - - /** - * Toggle mesh rendering on/off. - */ - private static void toggleMeshes() { - Spatial.CullHint hint = meshesNode.getLocalCullHint(); - if (hint == Spatial.CullHint.Inherit - || hint == Spatial.CullHint.Never) { - hint = Spatial.CullHint.Always; - } else if (hint == Spatial.CullHint.Always) { - hint = Spatial.CullHint.Never; - } - meshesNode.setCullHint(hint); - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - String message = "View: "; - - Spatial.CullHint cull = meshesNode.getLocalCullHint(); - message += (cull == Spatial.CullHint.Always) ? "NOmeshes" : "Meshes"; - - boolean debug = bulletAppState.isDebugEnabled(); - if (debug) { - message += "+" + describePhysicsDebugOptions(); - } - message += isPaused() ? " PAUSED" : ""; - statusText.setText(message); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyAsset; +import jme3utilities.MyString; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; + +/** + * Test/demonstrate double-ended New6Dof joints. + * + * @author Stephen Gold sgold@sonic.net + */ +public class JointDemo extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(JointDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = JointDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed in the upper-left corner of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * scene-graph node for visualizing solid objects + */ + final private static Node meshesNode = new Node("meshes node"); + /** + * motor to rotate the left-front leg + */ + private static RotationMotor lfMotor; + /** + * motor to rotate the left-rear leg + */ + private static RotationMotor lrMotor; + /** + * motor to rotate the right-front leg + */ + private static RotationMotor rfMotor; + /** + * motor to rotate the right-rear leg + */ + private static RotationMotor rrMotor; + // ************************************************************************* + // constructors + + /** + * Instantiate the JointDemo application. + */ + public JointDemo() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the JointDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new JointDemo(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + addLighting(); + + float length = 0.8f; + attachWorldAxes(length); + + float halfExtent = 50f; + float topY = 0f; + attachCubePlatform(halfExtent, topY); + + addRobot(); + + rootNode.attachChild(meshesNode); + meshesNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + statusText.setLocalTranslation(0f, cam.getHeight(), 0f); + guiNode.attachChild(statusText); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + super.configureDumper(); + + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.JointsInSpaces, true); + dumper.setEnabled(DumpFlags.Motors, true); + } + + /** + * Generate materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + ColorRGBA brown = new ColorRGBA().setAsSrgb(0.3f, 0.3f, 0.1f, 1f); + Material robotMaterial + = MyAsset.createShadedMaterial(assetManager, brown); + registerMaterial("robot", robotMaterial); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 0.5f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpScene, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + dim.bind("signal turnLF", KeyInput.KEY_NUMPAD7); + dim.bind("signal turnRF", KeyInput.KEY_NUMPAD9); + dim.bind("signal turnLR", KeyInput.KEY_NUMPAD1); + dim.bind("signal turnRR", KeyInput.KEY_NUMPAD3); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind("toggle view", KeyInput.KEY_SLASH); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "toggle view": + toggleMeshes(); + togglePhysicsDebug(); + return; + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + // Check UI signals and update motor velocities accordingly. + Signals signals = getSignals(); + + float lfVelocity = signals.test("turnLF") ? 2f : 0f; + lfMotor.set(MotorParam.TargetVelocity, lfVelocity); + + float rfVelocity = signals.test("turnRF") ? 2f : 0f; + rfMotor.set(MotorParam.TargetVelocity, rfVelocity); + + float lrVelocity = signals.test("turnLR") ? 2f : 0f; + lrMotor.set(MotorParam.TargetVelocity, lrVelocity); + + float rrVelocity = signals.test("turnRR") ? 2f : 0f; + rrMotor.set(MotorParam.TargetVelocity, rrVelocity); + + updateStatusText(); + } + // ************************************************************************* + // private methods + + /** + * Add a rectangular leg to the robot chassis. + * + * @param legGeom the geometry for the leg (not null) + * @param legInWorld the location of the leg (in world coordinates, not + * null) + * @param pivotInChassis the pivot offset relative to the chassis (not null) + * @param chassisInWorld the chassis location (in world coordinates, not + * null) + * @param chassisRbc the body for the chassis (not null) + * @return the new instance + */ + private RotationMotor addLeg(Geometry legGeom, Vector3f legInWorld, + Vector3f pivotInChassis, Vector3f chassisInWorld, + RigidBodyControl chassisRbc) { + meshesNode.attachChild(legGeom); + legGeom.move(legInWorld); + Material robotMaterial = findMaterial("robot"); + legGeom.setMaterial(robotMaterial); + + CollisionShape shape = new BoxCollisionShape(0.1f, 0.4f, 0.1f); + float mass = 0.1f; + RigidBodyControl legRbc = new RigidBodyControl(shape, mass); + legGeom.addControl(legRbc); + PhysicsSpace physicsSpace = getPhysicsSpace(); + legRbc.setPhysicsSpace(physicsSpace); + legRbc.setEnableSleep(false); + + Vector3f pivotInLeg + = pivotInChassis.add(chassisInWorld).subtractLocal(legInWorld); + New6Dof joint = new New6Dof(chassisRbc, legRbc, pivotInChassis, + pivotInLeg, new Matrix3f(), new Matrix3f(), RotationOrder.ZYX); + + // Inhibit X- and Y-axis rotations. + RotationMotor xMotor = joint.getRotationMotor(PhysicsSpace.AXIS_X); + xMotor.set(MotorParam.UpperLimit, 0f); + xMotor.set(MotorParam.LowerLimit, 0f); + RotationMotor yMotor = joint.getRotationMotor(PhysicsSpace.AXIS_Y); + yMotor.set(MotorParam.UpperLimit, 0f); + yMotor.set(MotorParam.LowerLimit, 0f); + + // Enable the motor for Z-axis rotation and return a reference to it. + RotationMotor zMotor = joint.getRotationMotor(PhysicsSpace.AXIS_Z); + zMotor.setMotorEnabled(true); + zMotor.set(MotorParam.MaxMotorForce, 9e9f); + + physicsSpace.addJoint(joint); + + return zMotor; + } + + /** + * Add lighting and shadows to the main scene. + */ + private void addLighting() { + ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootNode.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + sun.setName("sun"); + + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.5f); + viewPort.addProcessor(dlsr); + } + + /** + * Add a 4-legged robot to the scene. + */ + private void addRobot() { + Mesh chassisMesh = new Box(1f, 0.3f, 0.3f); + CollisionShape chassisShape = new BoxCollisionShape(1f, 0.3f, 0.3f); + Vector3f chassisInWorld = new Vector3f(0f, 1f, 0f); + + Geometry chassisGeom = new Geometry("chassis", chassisMesh); + meshesNode.attachChild(chassisGeom); + chassisGeom.move(chassisInWorld); + Material robotMaterial = findMaterial("robot"); + chassisGeom.setMaterial(robotMaterial); + chassisGeom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + float chassisMass = 1f; + RigidBodyControl chassisRbc + = new RigidBodyControl(chassisShape, chassisMass); + chassisGeom.addControl(chassisRbc); + PhysicsSpace physicsSpace = getPhysicsSpace(); + chassisRbc.setPhysicsSpace(physicsSpace); + chassisRbc.setEnableSleep(false); + + Mesh legMesh = new Box(0.1f, 0.4f, 0.1f); + + Geometry lfGeom = new Geometry("lf leg", legMesh); + Vector3f lfLegInWorld = new Vector3f(-0.8f, 0.6f, 0.5f); + Vector3f lfPivotInChassis = new Vector3f(-0.8f, 0f, 0.4f); + lfMotor = addLeg(lfGeom, lfLegInWorld, lfPivotInChassis, + chassisInWorld, chassisRbc); + + Geometry rfGeom = new Geometry("rf leg", legMesh); + Vector3f rfLegInWorld = new Vector3f(-0.8f, 0.6f, -0.5f); + Vector3f rfPivotInChassis = new Vector3f(-0.8f, 0f, -0.4f); + rfMotor = addLeg(rfGeom, rfLegInWorld, rfPivotInChassis, + chassisInWorld, chassisRbc); + + Geometry lrGeom = new Geometry("lr leg", legMesh); + Vector3f lrLegInWorld = new Vector3f(0.8f, 0.6f, 0.5f); + Vector3f lrPivotInChassis = new Vector3f(0.8f, 0f, 0.4f); + lrMotor = addLeg(lrGeom, lrLegInWorld, lrPivotInChassis, + chassisInWorld, chassisRbc); + + Geometry rrGeom = new Geometry("rr leg", legMesh); + Vector3f rrLegInWorld = new Vector3f(0.8f, 0.6f, -0.5f); + Vector3f rrPivotInChassis = new Vector3f(0.8f, 0f, -0.4f); + rrMotor = addLeg(rrGeom, rrLegInWorld, rrPivotInChassis, + chassisInWorld, chassisRbc); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(4f); + flyCam.setZoomSpeed(4f); + + cam.setLocation(new Vector3f(2.65f, 2.42f, 9.37f)); + cam.setRotation(new Quaternion(0f, 0.9759f, -0.04f, -0.2136f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + } + + /** + * Toggle mesh rendering on/off. + */ + private static void toggleMeshes() { + Spatial.CullHint hint = meshesNode.getLocalCullHint(); + if (hint == Spatial.CullHint.Inherit + || hint == Spatial.CullHint.Never) { + hint = Spatial.CullHint.Always; + } else if (hint == Spatial.CullHint.Always) { + hint = Spatial.CullHint.Never; + } + meshesNode.setCullHint(hint); + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + String message = "View: "; + + Spatial.CullHint cull = meshesNode.getLocalCullHint(); + message += (cull == Spatial.CullHint.Always) ? "NOmeshes" : "Meshes"; + + boolean debug = bulletAppState.isDebugEnabled(); + if (debug) { + message += "+" + describePhysicsDebugOptions(); + } + message += isPaused() ? " PAUSED" : ""; + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticity.java b/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticity.java index 00f027c57..23b192b75 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticity.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticity.java @@ -1,402 +1,402 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.SolverInfo; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.joints.HingeJoint; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.BackingStoreException; -import jme3utilities.Heart; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; - -/** - * Tune the simulation quality of a single-ended HingeJoint. - * - * @author Stephen Gold sgold@sonic.net - */ -public class JointElasticity extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * half the width of the door (in physics-space units) - */ - final private static float doorHalfWidth = 2f; - /** - * mass of the door (in physics mass units) - */ - final private static float doorMass = 1f; - /** - * half the thickness of the door (in physics-space units) - */ - final private static float halfThickness = 0.3f; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(JointElasticity.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = JointElasticity.class.getSimpleName(); - /** - * action string to advance to the next field - */ - final private static String asNextField = "next field"; - /** - * action string to advance to the next value - */ - final private static String asNextValue = "next value"; - /** - * action string to go to the previous field - */ - final private static String asPreviousField = "previous field"; - /** - * action string to go to the previous value - */ - final private static String asPreviousValue = "previous value"; - /** - * signal names for the CameraOrbitAppState - */ - final private static String signalOrbitLeft = "cameraOrbitLeft"; - final private static String signalOrbitRight = "cameraOrbitRight"; - // ************************************************************************* - // fields - - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * AppState to manage the status overlay - */ - private static JointElasticityStatus status; - /** - * dynamic ball - */ - private static PhysicsRigidBody ballBody; - /** - * dynamic door - */ - private static PhysicsRigidBody doorBody; - // ************************************************************************* - // constructors - - /** - * Instantiate the JointElasticity application. - */ - public JointElasticity() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the JointElasticity application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - try { - settings.load(applicationName); - } catch (BackingStoreException exception) { - logger.warning("Failed to load AppSettings."); - } - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setTitle(title); // Customize the window's title bar. - - JointElasticity application = new JointElasticity(); - application.setSettings(settings); - application.start(); - } - - /** - * Alter the mass ratio during a scenario. - * - * @param massRatio the mass of the ball as a multiple of the door's mass - * (>0) - */ - static void setMassRatio(float massRatio) { - Validate.positive(massRatio, "mass ratio"); - - float newMass = massRatio * doorMass; - ballBody.setMass(newMass); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - status = new JointElasticityStatus(); - boolean success = stateManager.attach(status); - assert success; - - super.acorusInit(); - - configureCamera(); - configureDumper(); - configurePhysics(); - restartScenario(); - - ColorRGBA bgColor = new ColorRGBA(0.04f, 0.04f, 0.04f, 1f); - viewPort.setBackgroundColor(bgColor); - - setSpeed(0.2f); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 1f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpScenes, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bind(asNextField, KeyInput.KEY_NUMPAD2); - dim.bind(asNextValue, KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal(signalOrbitLeft, KeyInput.KEY_LEFT); - dim.bindSignal(signalOrbitRight, KeyInput.KEY_RIGHT); - - dim.bind(asPreviousField, KeyInput.KEY_NUMPAD8); - dim.bind(asPreviousValue, KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); - - dim.bind("restart", KeyInput.KEY_NUMPAD5, KeyInput.KEY_SPACE, - KeyInput.KEY_RETURN); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - } - - /** - * Process an action that wasn't handled by the active InputMode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case asNextField: - status.advanceSelectedField(+1); - return; - case asNextValue: - status.advanceValue(+1); - return; - case asPreviousField: - status.advanceSelectedField(-1); - return; - case asPreviousValue: - status.advanceValue(-1); - return; - - case "restart": - restartScenario(); - return; - - default: - } - String[] words = actionString.split(" "); - if (words.length == 2 && "load".equals(words[0])) { - return; - } - } - - // The action is not handled: forward it to the superclass. - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - status.resize(newWidth, newHeight); - super.onViewPortResize(newWidth, newHeight); - } - // ************************************************************************* - // private methods - - /** - * Create a kinematic rigid body with a sphere shape and add it to the - * space. - */ - private void addBall() { - float radius = 0.4f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - - float mass = status.massRatio() * doorMass; - ballBody = new PhysicsRigidBody(shape, mass); - ballBody.setLinearVelocity(new Vector3f(2f, 0f, -10f)); - ballBody.setPhysicsLocation(new Vector3f(0.8f, 0f, 2f)); - addCollisionObject(ballBody); - } - - /** - * Create a dynamic body with a box shape and add it to the specified - * PhysicsSpace. - */ - private void addDoor() { - float halfHeight = 4f; - BoxCollisionShape shape = new BoxCollisionShape( - doorHalfWidth, halfHeight, halfThickness); - doorBody = new PhysicsRigidBody(shape, doorMass); - addCollisionObject(doorBody); - - // Disable sleep (deactivation). - doorBody.setEnableSleep(false); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.1f; - float far = 500f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(10f); - flyCam.setZoomSpeed(10f); - - cam.setLocation(new Vector3f(0f, 8.8f, 6.2f)); - cam.setRotation(new Quaternion(0f, 0.9f, -0.43589f, 0f)); - - AppState orbitState = new CameraOrbitAppState( - cam, signalOrbitRight, signalOrbitLeft); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - bulletAppState.setDebugEnabled(true); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - SolverInfo info = physicsSpace.getSolverInfo(); - - float erp = status.jointErp(); - info.setJointErp(erp); - - int numIterations = status.numIterations(); - info.setNumIterations(numIterations); - - float timestep = status.timeStep(); - physicsSpace.setAccuracy(timestep); - } - - /** - * Restart the scenario. - */ - private void restartScenario() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.destroy(); - - // Add a dynamic body for the door. - addDoor(); - - // Add a single-ended hinge joint to constrain the door's motion. - Vector3f pivotInDoor = new Vector3f(doorHalfWidth, 0f, 0f); - Vector3f pivotInWorld = new Vector3f(doorHalfWidth, 0f, 0f); - HingeJoint joint = new HingeJoint(doorBody, pivotInDoor, pivotInWorld, - Vector3f.UNIT_Y, Vector3f.UNIT_Y, JointEnd.B); - float lowAngle = 0f; - float highAngle = 0f; - joint.setLimit(lowAngle, highAngle); // disable rotation - addJoint(joint); - - // Add a dynamic, yellow ball. - addBall(); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.SolverInfo; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.joints.HingeJoint; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.BackingStoreException; +import jme3utilities.Heart; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; + +/** + * Tune the simulation quality of a single-ended HingeJoint. + * + * @author Stephen Gold sgold@sonic.net + */ +public class JointElasticity extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * half the width of the door (in physics-space units) + */ + final private static float doorHalfWidth = 2f; + /** + * mass of the door (in physics mass units) + */ + final private static float doorMass = 1f; + /** + * half the thickness of the door (in physics-space units) + */ + final private static float halfThickness = 0.3f; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(JointElasticity.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = JointElasticity.class.getSimpleName(); + /** + * action string to advance to the next field + */ + final private static String asNextField = "next field"; + /** + * action string to advance to the next value + */ + final private static String asNextValue = "next value"; + /** + * action string to go to the previous field + */ + final private static String asPreviousField = "previous field"; + /** + * action string to go to the previous value + */ + final private static String asPreviousValue = "previous value"; + /** + * signal names for the CameraOrbitAppState + */ + final private static String signalOrbitLeft = "cameraOrbitLeft"; + final private static String signalOrbitRight = "cameraOrbitRight"; + // ************************************************************************* + // fields + + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * AppState to manage the status overlay + */ + private static JointElasticityStatus status; + /** + * dynamic ball + */ + private static PhysicsRigidBody ballBody; + /** + * dynamic door + */ + private static PhysicsRigidBody doorBody; + // ************************************************************************* + // constructors + + /** + * Instantiate the JointElasticity application. + */ + public JointElasticity() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the JointElasticity application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + try { + settings.load(applicationName); + } catch (BackingStoreException exception) { + logger.warning("Failed to load AppSettings."); + } + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setTitle(title); // Customize the window's title bar. + + JointElasticity application = new JointElasticity(); + application.setSettings(settings); + application.start(); + } + + /** + * Alter the mass ratio during a scenario. + * + * @param massRatio the mass of the ball as a multiple of the door's mass + * (>0) + */ + static void setMassRatio(float massRatio) { + Validate.positive(massRatio, "mass ratio"); + + float newMass = massRatio * doorMass; + ballBody.setMass(newMass); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + status = new JointElasticityStatus(); + boolean success = stateManager.attach(status); + assert success; + + super.acorusInit(); + + configureCamera(); + configureDumper(); + configurePhysics(); + restartScenario(); + + ColorRGBA bgColor = new ColorRGBA(0.04f, 0.04f, 0.04f, 1f); + viewPort.setBackgroundColor(bgColor); + + setSpeed(0.2f); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 1f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpScenes, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bind(asNextField, KeyInput.KEY_NUMPAD2); + dim.bind(asNextValue, KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal(signalOrbitLeft, KeyInput.KEY_LEFT); + dim.bindSignal(signalOrbitRight, KeyInput.KEY_RIGHT); + + dim.bind(asPreviousField, KeyInput.KEY_NUMPAD8); + dim.bind(asPreviousValue, KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); + + dim.bind("restart", KeyInput.KEY_NUMPAD5, KeyInput.KEY_SPACE, + KeyInput.KEY_RETURN); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + } + + /** + * Process an action that wasn't handled by the active InputMode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case asNextField: + status.advanceSelectedField(+1); + return; + case asNextValue: + status.advanceValue(+1); + return; + case asPreviousField: + status.advanceSelectedField(-1); + return; + case asPreviousValue: + status.advanceValue(-1); + return; + + case "restart": + restartScenario(); + return; + + default: + } + String[] words = actionString.split(" "); + if (words.length == 2 && "load".equals(words[0])) { + return; + } + } + + // The action is not handled: forward it to the superclass. + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + status.resize(newWidth, newHeight); + super.onViewPortResize(newWidth, newHeight); + } + // ************************************************************************* + // private methods + + /** + * Create a kinematic rigid body with a sphere shape and add it to the + * space. + */ + private void addBall() { + float radius = 0.4f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + + float mass = status.massRatio() * doorMass; + ballBody = new PhysicsRigidBody(shape, mass); + ballBody.setLinearVelocity(new Vector3f(2f, 0f, -10f)); + ballBody.setPhysicsLocation(new Vector3f(0.8f, 0f, 2f)); + addCollisionObject(ballBody); + } + + /** + * Create a dynamic body with a box shape and add it to the specified + * PhysicsSpace. + */ + private void addDoor() { + float halfHeight = 4f; + BoxCollisionShape shape = new BoxCollisionShape( + doorHalfWidth, halfHeight, halfThickness); + doorBody = new PhysicsRigidBody(shape, doorMass); + addCollisionObject(doorBody); + + // Disable sleep (deactivation). + doorBody.setEnableSleep(false); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.1f; + float far = 500f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(10f); + flyCam.setZoomSpeed(10f); + + cam.setLocation(new Vector3f(0f, 8.8f, 6.2f)); + cam.setRotation(new Quaternion(0f, 0.9f, -0.43589f, 0f)); + + AppState orbitState = new CameraOrbitAppState( + cam, signalOrbitRight, signalOrbitLeft); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + bulletAppState.setDebugEnabled(true); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + SolverInfo info = physicsSpace.getSolverInfo(); + + float erp = status.jointErp(); + info.setJointErp(erp); + + int numIterations = status.numIterations(); + info.setNumIterations(numIterations); + + float timestep = status.timeStep(); + physicsSpace.setAccuracy(timestep); + } + + /** + * Restart the scenario. + */ + private void restartScenario() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.destroy(); + + // Add a dynamic body for the door. + addDoor(); + + // Add a single-ended hinge joint to constrain the door's motion. + Vector3f pivotInDoor = new Vector3f(doorHalfWidth, 0f, 0f); + Vector3f pivotInWorld = new Vector3f(doorHalfWidth, 0f, 0f); + HingeJoint joint = new HingeJoint(doorBody, pivotInDoor, pivotInWorld, + Vector3f.UNIT_Y, Vector3f.UNIT_Y, JointEnd.B); + float lowAngle = 0f; + float highAngle = 0f; + joint.setLimit(lowAngle, highAngle); // disable rotation + addJoint(joint); + + // Add a dynamic, yellow ball. + addBall(); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticityStatus.java b/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticityStatus.java index 0db1ad806..133eb4282 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticityStatus.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/JointElasticityStatus.java @@ -1,359 +1,359 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.math.ColorRGBA; -import java.util.Arrays; -import java.util.logging.Logger; -import jme3utilities.SimpleAppState; -import jme3utilities.math.MyArray; -import jme3utilities.math.MyMath; -import jme3utilities.ui.AcorusDemo; - -/** - * AppState to display the status of the JointElasticity application in an - * overlay. The overlay consists of status lines, one of which is selected for - * editing. The overlay is located in the upper-left portion of the display. - * - * @author Stephen Gold sgold@sonic.net - */ -final class JointElasticityStatus extends SimpleAppState { - // ************************************************************************* - // constants and loggers - - /** - * list of error-reduction parameter values, in ascending order - */ - final private static float[] erpValues - = {0.01f, 0.1f, 0.2f, 0.5f, 0.8f, 0.9f, 1f}; - /** - * list of mass ratios, in ascending order - */ - final private static float[] ratioValues = {1f, 10f, 100f, 1000f}; - /** - * list of physics timesteps, in ascending order - */ - final private static float[] timestepValues - = {0.002f, 0.003f, 0.005f, 0.01f, 1f / 60, 0.04f}; - /** - * list of constraint-solver iteration counts, in ascending order - */ - final private static int[] iterationsValues - = {10, 250, 500, 1000, 2000, 4000}; - /** - * index of the status line for the joint ERP value - */ - final private static int erpStatusLine = 0; - /** - * index of the status line for the constraint-solver iteration count - */ - final private static int iterationsStatusLine = 1; - /** - * index of the status line for the mass ratio - */ - final private static int ratioStatusLine = 2; - /** - * index of the status line for physics timestep - */ - final private static int timestepStatusLine = 3; - /** - * number of lines of text in the overlay - */ - final private static int numStatusLines = 4; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(JointElasticityStatus.class.getName()); - // ************************************************************************* - // fields - - /** - * lines of text displayed in the upper-left corner of the GUI node ([0] is - * the top line) - */ - final private BitmapText[] statusLines = new BitmapText[numStatusLines]; - /** - * error-reduction parameter value for joints - */ - private float jointErp = 0.2f; - /** - * ball's mass as a multiple of the door - */ - private float massRatio = 1f; - /** - * physics timestep (in seconds) - */ - private float timestep = 1f / 60; - /** - * maximum number of solver iterations per physics timestep - */ - private int numIterations = 10; - /** - * index of the line being edited (≥1) - */ - private int selectedLine = timestepStatusLine; - /** - * reference to the application instance - */ - private JointElasticity appInstance; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized enabled state. - */ - JointElasticityStatus() { - super(true); - } - // ************************************************************************* - // new methods exposed - - /** - * Advance the field selection by the specified amount. - * - * @param amount the number of fields to move downward - */ - void advanceSelectedField(int amount) { - int firstField = 0; - int numFields = numStatusLines - firstField; - - int selectedField = selectedLine - firstField; - int sum = selectedField + amount; - selectedField = MyMath.modulo(sum, numFields); - this.selectedLine = selectedField + firstField; - } - - /** - * Advance the value of the selected field by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - void advanceValue(int amount) { - switch (selectedLine) { - case erpStatusLine: - advanceJointErp(amount); - break; - - case iterationsStatusLine: - advanceIterations(amount); - break; - - case ratioStatusLine: - advanceRatio(amount); - break; - - case timestepStatusLine: - advanceTimestep(amount); - break; - - default: - throw new IllegalStateException("line = " + selectedLine); - } - } - - float jointErp() { - assert jointErp >= 0f : jointErp; - assert jointErp <= 1f : jointErp; - return jointErp; - } - - float massRatio() { - assert massRatio > 0f : massRatio; - return massRatio; - } - - int numIterations() { - assert numIterations > 0 : numIterations; - return numIterations; - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - void resize(int newWidth, int newHeight) { - if (isInitialized()) { - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - } - } - - float timeStep() { - assert timestep > 0f : timestep; - return timestep; - } - // ************************************************************************* - // ActionAppState methods - - /** - * Clean up this AppState during the first update after it gets detached. - * Should be invoked only by a subclass or by the AppStateManager. - */ - @Override - public void cleanup() { - super.cleanup(); - - // Remove the status lines from the guiNode. - for (int i = 0; i < numStatusLines; ++i) { - statusLines[i].removeFromParent(); - } - } - - /** - * Initialize this AppState on the first update after it gets attached. - * - * @param sm application's state manager (not null) - * @param app application which owns this state (not null) - */ - @Override - public void initialize(AppStateManager sm, Application app) { - super.initialize(sm, app); - - this.appInstance = (JointElasticity) app; - BitmapFont guiFont - = assetManager.loadFont("Interface/Fonts/Default.fnt"); - - // Add status lines to the guiNode. - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - float y = cam.getHeight() - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - guiNode.attachChild(statusLines[lineIndex]); - } - - assert MyArray.isSorted(iterationsValues); - assert MyArray.isSorted(ratioValues); - assert MyArray.isSorted(timestepValues); - } - - /** - * Callback to update this AppState prior to rendering. (Invoked once per - * frame while the state is attached and enabled.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - int index = 1 + Arrays.binarySearch(erpValues, jointErp); - String message = String.format("Joint ERP (#%d of %d): %.2f", - index, erpValues.length, jointErp); - updateStatusLine(erpStatusLine, message); - - index = 1 + Arrays.binarySearch(iterationsValues, numIterations); - message = String.format("Max solver iterations (#%d of %d): %d", - index, iterationsValues.length, numIterations); - updateStatusLine(iterationsStatusLine, message); - - index = 1 + Arrays.binarySearch(ratioValues, massRatio); - message = String.format("Mass ratio (#%d of %d): %.0f : 1", - index, ratioValues.length, massRatio); - updateStatusLine(ratioStatusLine, message); - - index = 1 + Arrays.binarySearch(timestepValues, timestep); - message = String.format("Timestep (#%d of %d): %.4f second", - index, timestepValues.length, timestep); - updateStatusLine(timestepStatusLine, message); - } - // ************************************************************************* - // private methods - - /** - * Advance the iteration-count selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceIterations(int amount) { - numIterations = AcorusDemo.advanceInt( - iterationsValues, numIterations, amount); - PhysicsSpace physicsSpace = appInstance.getPhysicsSpace(); - physicsSpace.getSolverInfo().setNumIterations(numIterations); - } - - /** - * Advance the joint ERP value by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceJointErp(int amount) { - jointErp = AcorusDemo.advanceFloat(erpValues, jointErp, amount); - PhysicsSpace physicsSpace = appInstance.getPhysicsSpace(); - physicsSpace.getSolverInfo().setJointErp(jointErp); - } - - /** - * Advance the mass ratio by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceRatio(int amount) { - this.massRatio = AcorusDemo.advanceFloat( - ratioValues, massRatio, amount); - JointElasticity.setMassRatio(massRatio); - } - - /** - * Advance the timestep selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceTimestep(int amount) { - this.timestep = AcorusDemo.advanceFloat( - timestepValues, timestep, amount); - PhysicsSpace physicsSpace = appInstance.getPhysicsSpace(); - physicsSpace.setAccuracy(timestep); - } - - /** - * Update the indexed status line. - * - * @param lineIndex which status line (≥0) - * @param text the text to display, not including the arrow, if any - */ - private void updateStatusLine(int lineIndex, String text) { - BitmapText spatial = statusLines[lineIndex]; - - if (lineIndex == selectedLine) { - spatial.setColor(ColorRGBA.Yellow.clone()); - spatial.setText("--> " + text); - } else { - spatial.setColor(ColorRGBA.White.clone()); - spatial.setText(" " + text); - } - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.math.ColorRGBA; +import java.util.Arrays; +import java.util.logging.Logger; +import jme3utilities.SimpleAppState; +import jme3utilities.math.MyArray; +import jme3utilities.math.MyMath; +import jme3utilities.ui.AcorusDemo; + +/** + * AppState to display the status of the JointElasticity application in an + * overlay. The overlay consists of status lines, one of which is selected for + * editing. The overlay is located in the upper-left portion of the display. + * + * @author Stephen Gold sgold@sonic.net + */ +final class JointElasticityStatus extends SimpleAppState { + // ************************************************************************* + // constants and loggers + + /** + * list of error-reduction parameter values, in ascending order + */ + final private static float[] erpValues + = {0.01f, 0.1f, 0.2f, 0.5f, 0.8f, 0.9f, 1f}; + /** + * list of mass ratios, in ascending order + */ + final private static float[] ratioValues = {1f, 10f, 100f, 1000f}; + /** + * list of physics timesteps, in ascending order + */ + final private static float[] timestepValues + = {0.002f, 0.003f, 0.005f, 0.01f, 1f / 60, 0.04f}; + /** + * list of constraint-solver iteration counts, in ascending order + */ + final private static int[] iterationsValues + = {10, 250, 500, 1000, 2000, 4000}; + /** + * index of the status line for the joint ERP value + */ + final private static int erpStatusLine = 0; + /** + * index of the status line for the constraint-solver iteration count + */ + final private static int iterationsStatusLine = 1; + /** + * index of the status line for the mass ratio + */ + final private static int ratioStatusLine = 2; + /** + * index of the status line for physics timestep + */ + final private static int timestepStatusLine = 3; + /** + * number of lines of text in the overlay + */ + final private static int numStatusLines = 4; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(JointElasticityStatus.class.getName()); + // ************************************************************************* + // fields + + /** + * lines of text displayed in the upper-left corner of the GUI node ([0] is + * the top line) + */ + final private BitmapText[] statusLines = new BitmapText[numStatusLines]; + /** + * error-reduction parameter value for joints + */ + private float jointErp = 0.2f; + /** + * ball's mass as a multiple of the door + */ + private float massRatio = 1f; + /** + * physics timestep (in seconds) + */ + private float timestep = 1f / 60; + /** + * maximum number of solver iterations per physics timestep + */ + private int numIterations = 10; + /** + * index of the line being edited (≥1) + */ + private int selectedLine = timestepStatusLine; + /** + * reference to the application instance + */ + private JointElasticity appInstance; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized enabled state. + */ + JointElasticityStatus() { + super(true); + } + // ************************************************************************* + // new methods exposed + + /** + * Advance the field selection by the specified amount. + * + * @param amount the number of fields to move downward + */ + void advanceSelectedField(int amount) { + int firstField = 0; + int numFields = numStatusLines - firstField; + + int selectedField = selectedLine - firstField; + int sum = selectedField + amount; + selectedField = MyMath.modulo(sum, numFields); + this.selectedLine = selectedField + firstField; + } + + /** + * Advance the value of the selected field by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + void advanceValue(int amount) { + switch (selectedLine) { + case erpStatusLine: + advanceJointErp(amount); + break; + + case iterationsStatusLine: + advanceIterations(amount); + break; + + case ratioStatusLine: + advanceRatio(amount); + break; + + case timestepStatusLine: + advanceTimestep(amount); + break; + + default: + throw new IllegalStateException("line = " + selectedLine); + } + } + + float jointErp() { + assert jointErp >= 0f : jointErp; + assert jointErp <= 1f : jointErp; + return jointErp; + } + + float massRatio() { + assert massRatio > 0f : massRatio; + return massRatio; + } + + int numIterations() { + assert numIterations > 0 : numIterations; + return numIterations; + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + void resize(int newWidth, int newHeight) { + if (isInitialized()) { + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + } + } + + float timeStep() { + assert timestep > 0f : timestep; + return timestep; + } + // ************************************************************************* + // ActionAppState methods + + /** + * Clean up this AppState during the first update after it gets detached. + * Should be invoked only by a subclass or by the AppStateManager. + */ + @Override + public void cleanup() { + super.cleanup(); + + // Remove the status lines from the guiNode. + for (int i = 0; i < numStatusLines; ++i) { + statusLines[i].removeFromParent(); + } + } + + /** + * Initialize this AppState on the first update after it gets attached. + * + * @param sm application's state manager (not null) + * @param app application which owns this state (not null) + */ + @Override + public void initialize(AppStateManager sm, Application app) { + super.initialize(sm, app); + + this.appInstance = (JointElasticity) app; + BitmapFont guiFont + = assetManager.loadFont("Interface/Fonts/Default.fnt"); + + // Add status lines to the guiNode. + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + float y = cam.getHeight() - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + guiNode.attachChild(statusLines[lineIndex]); + } + + assert MyArray.isSorted(iterationsValues); + assert MyArray.isSorted(ratioValues); + assert MyArray.isSorted(timestepValues); + } + + /** + * Callback to update this AppState prior to rendering. (Invoked once per + * frame while the state is attached and enabled.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + int index = 1 + Arrays.binarySearch(erpValues, jointErp); + String message = String.format("Joint ERP (#%d of %d): %.2f", + index, erpValues.length, jointErp); + updateStatusLine(erpStatusLine, message); + + index = 1 + Arrays.binarySearch(iterationsValues, numIterations); + message = String.format("Max solver iterations (#%d of %d): %d", + index, iterationsValues.length, numIterations); + updateStatusLine(iterationsStatusLine, message); + + index = 1 + Arrays.binarySearch(ratioValues, massRatio); + message = String.format("Mass ratio (#%d of %d): %.0f : 1", + index, ratioValues.length, massRatio); + updateStatusLine(ratioStatusLine, message); + + index = 1 + Arrays.binarySearch(timestepValues, timestep); + message = String.format("Timestep (#%d of %d): %.4f second", + index, timestepValues.length, timestep); + updateStatusLine(timestepStatusLine, message); + } + // ************************************************************************* + // private methods + + /** + * Advance the iteration-count selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceIterations(int amount) { + numIterations = AcorusDemo.advanceInt( + iterationsValues, numIterations, amount); + PhysicsSpace physicsSpace = appInstance.getPhysicsSpace(); + physicsSpace.getSolverInfo().setNumIterations(numIterations); + } + + /** + * Advance the joint ERP value by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceJointErp(int amount) { + jointErp = AcorusDemo.advanceFloat(erpValues, jointErp, amount); + PhysicsSpace physicsSpace = appInstance.getPhysicsSpace(); + physicsSpace.getSolverInfo().setJointErp(jointErp); + } + + /** + * Advance the mass ratio by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceRatio(int amount) { + this.massRatio = AcorusDemo.advanceFloat( + ratioValues, massRatio, amount); + JointElasticity.setMassRatio(massRatio); + } + + /** + * Advance the timestep selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceTimestep(int amount) { + this.timestep = AcorusDemo.advanceFloat( + timestepValues, timestep, amount); + PhysicsSpace physicsSpace = appInstance.getPhysicsSpace(); + physicsSpace.setAccuracy(timestep); + } + + /** + * Update the indexed status line. + * + * @param lineIndex which status line (≥0) + * @param text the text to display, not including the arrow, if any + */ + private void updateStatusLine(int lineIndex, String text) { + BitmapText spatial = statusLines[lineIndex]; + + if (lineIndex == selectedLine) { + spatial.setColor(ColorRGBA.Yellow.clone()); + spatial.setText("--> " + text); + } else { + spatial.setColor(ColorRGBA.White.clone()); + spatial.setText(" " + text); + } + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/MultiSphereDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/MultiSphereDemo.java index dd4be6022..6a10c0663 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/MultiSphereDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/MultiSphereDemo.java @@ -1,93 +1,93 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; - -/** - * Test/demonstrate various collision shapes. Maintained for historical - * continuity, this is now essentially a wrapper for DropTest. - *

- * Seen in the November 2018 demo video: - * https://www.youtube.com/watch?v=OS2zjB01c6E - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MultiSphereDemo { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(MultiSphereDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = MultiSphereDemo.class.getSimpleName(); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MultiSphereDemo() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the MultiSphereDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new DropTest(); - application.setSettings(settings); - application.start(); - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; + +/** + * Test/demonstrate various collision shapes. Maintained for historical + * continuity, this is now essentially a wrapper for DropTest. + *

+ * Seen in the November 2018 demo video: + * https://www.youtube.com/watch?v=OS2zjB01c6E + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MultiSphereDemo { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(MultiSphereDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = MultiSphereDemo.class.getSimpleName(); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MultiSphereDemo() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the MultiSphereDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new DropTest(); + application.setSettings(settings); + application.start(); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/NewtonsCradle.java b/MinieExamples/src/main/java/jme3utilities/minie/test/NewtonsCradle.java index 261c6209c..6f560f6b2 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/NewtonsCradle.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/NewtonsCradle.java @@ -1,476 +1,476 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.Point2PointJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.RectSizeLimits; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.DisplaySettings; -import jme3utilities.ui.DsEditOverlay; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.ShowDialog; - -/** - * A physics demo that simulates a Newton's cradle. - *

- * https://en.wikipedia.org/wiki/Newton%27s_cradle - *

- * Collision objects are rendered entirely by debug visualization. - * - * @author Stephen Gold sgold@sonic.net - */ -public class NewtonsCradle - extends PhysicsDemo - implements DebugInitListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(NewtonsCradle.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = NewtonsCradle.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed at the bottom of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * proposed display settings, for the DsEditOverlay - */ - private static DisplaySettings proposedSettings; - /** - * temporary storage used in updateStatusText() - */ - final private static Vector3f tmpVector = new Vector3f(); - // ************************************************************************* - // constructors - - /** - * Instantiate the NewtonsCradle application. - */ - public NewtonsCradle() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the NewtonsCradle application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - for (String arg : arguments) { - switch (arg) { - case "--deleteOnly": - Heart.deleteStoredSettings(applicationName); - return; - default: - } - } - - NewtonsCradle application = new NewtonsCradle(); - RectSizeLimits sizeLimits = new RectSizeLimits( - 480, 240, // min width, height - 2_048, 1_080 // max width, height - ); - final String title = applicationName + " " + MyString.join(arguments); - proposedSettings = new DisplaySettings( - application, applicationName, sizeLimits) { - @Override - protected void applyOverrides(AppSettings settings) { - setShowDialog(ShowDialog.Never); - settings.setAudioRenderer(null); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - if (settings.getSamples() < 1) { - settings.setSamples(4); // anti-aliasing - } - settings.setResizable(true); - settings.setTitle(title); // Customize the window's title bar. - } - }; - - AppSettings appSettings = proposedSettings.initialize(); - if (appSettings != null) { - application.setSettings(appSettings); - /* - * If the settings dialog should be shown, - * it has already been shown by DisplaySettings.initialize(). - */ - application.setShowSettings(false); - - application.start(); - } - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - statusText.setLocalTranslation(205f, 25f, 0f); - guiNode.attachChild(statusText); - - AppState dseOverlay = new DsEditOverlay(proposedSettings); - boolean success = stateManager.attach(dseOverlay); - assert success; - - super.acorusInit(); - - restartSimulation(5); - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - ColorRGBA black = new ColorRGBA(0.01f, 0.01f, 0.01f, 1f); - Material ball = MyAsset.createShinyMaterial(assetManager, black); - ball.setFloat("Shininess", 100f); - registerMaterial("ball", ball); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of debug axis arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 12f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind("simulate 1", KeyInput.KEY_F1, KeyInput.KEY_1, - KeyInput.KEY_NUMPAD1); - dim.bind("simulate 2", KeyInput.KEY_F2, KeyInput.KEY_2, - KeyInput.KEY_NUMPAD2); - dim.bind("simulate 3", KeyInput.KEY_F3, KeyInput.KEY_3, - KeyInput.KEY_NUMPAD3); - dim.bind("simulate 4", KeyInput.KEY_F4, KeyInput.KEY_4, - KeyInput.KEY_NUMPAD4); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case asEditDisplaySettings: - InputMode editor = InputMode.findMode("dsEdit"); - InputMode.suspendAndActivate(editor); - return; - - case "simulate 1": - restartSimulation(1); - return; - case "simulate 2": - restartSimulation(2); - return; - case "simulate 3": - restartSimulation(3); - return; - case "simulate 4": - restartSimulation(4); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Callback invoked after the framebuffer is resized. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - proposedSettings.resize(newWidth, newHeight); - DsEditOverlay dseOverlay = stateManager.getState(DsEditOverlay.class); - dseOverlay.onViewPortResize(newWidth, newHeight); - - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusText(); - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private static void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootSpatial.addLight(sun); - sun.setName("sun"); - } - - /** - * Add a dynamic ball to the PhysicsSpace, suspended between 2 single-ended - * point-to-point joints. - * - * @param xOffset the desired X coordinate for the ball's center - * @return the new instance (not null) - */ - private PhysicsRigidBody addSuspendedBall(float xOffset) { - float radius = 9.9f; - SphereCollisionShape sphere = new SphereCollisionShape(radius); - PhysicsRigidBody ball = new PhysicsRigidBody(sphere); - Vector3f location = new Vector3f(xOffset, 0f, 0f); - Material material = findMaterial("ball"); - ball.setDebugMaterial(material); - ball.setDebugMeshNormals(MeshNormals.Sphere); - ball.setDebugMeshResolution(DebugShapeFactory.highResolution); - ball.setFriction(0f); - ball.setPhysicsLocation(location); - ball.setRestitution(1f); - addCollisionObject(ball); - - float wireLength = 80f; - float yOffset = wireLength * MyMath.rootHalf; - - Vector3f offset = new Vector3f(0f, yOffset, +yOffset); - Point2PointJoint joint1 = new Point2PointJoint(ball, offset); - addJoint(joint1); - - offset.set(0f, yOffset, -yOffset); - Point2PointJoint joint2 = new Point2PointJoint(ball, offset); - addJoint(joint2); - - return ball; - } - - /** - * Remove all physics objects from the PhysicsSpace. - */ - private void clearSpace() { - stateManager.detach(bulletAppState); - configurePhysics(); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(40f); - flyCam.setZoomSpeed(40f); - - cam.setLocation(new Vector3f(72f, 35f, 140f)); - cam.setRotation(new Quaternion(0.001f, 0.96926f, -0.031f, -0.244f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugInitListener(this); - stateManager.attach(bulletAppState); - - getPhysicsSpace().setAccuracy(0.01f); - setGravityAll(150f); - } - - /** - * Restart the simulation (paused) with the specified number of balls. - * - * @param numBalls (≥1) - */ - private void restartSimulation(int numBalls) { - clearSpace(); - this.speed = pausedSpeed; - - float xSeparation = 20f; - - // center-to-center separation between the first and last balls - float xExtent = (numBalls - 1) * xSeparation; - - float x0 = -xExtent / 2; - PhysicsRigidBody[] balls = new PhysicsRigidBody[numBalls]; - for (int ballIndex = 0; ballIndex < numBalls; ++ballIndex) { - float x = x0 + ballIndex * xSeparation; - balls[ballIndex] = addSuspendedBall(x); - } - - Vector3f kick = new Vector3f(-20f * numBalls, 0f, 0f); - balls[0].applyCentralImpulse(kick); - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - Vector3f gravity = physicsSpace.getGravity(null); - double totalEnergy = 0.0; - for (PhysicsRigidBody body : physicsSpace.getRigidBodyList()) { - totalEnergy += body.kineticEnergy(); - - body.getPhysicsLocation(tmpVector); - double potentialEnergy = -MyVector3f.dot(tmpVector, gravity); - totalEnergy += potentialEnergy; - } - - String message = String.format( - "KE+PE=%f%s", totalEnergy, isPaused() ? " PAUSED" : ""); - statusText.setText(message); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.Point2PointJoint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.RectSizeLimits; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.DisplaySettings; +import jme3utilities.ui.DsEditOverlay; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.ShowDialog; + +/** + * A physics demo that simulates a Newton's cradle. + *

+ * https://en.wikipedia.org/wiki/Newton%27s_cradle + *

+ * Collision objects are rendered entirely by debug visualization. + * + * @author Stephen Gold sgold@sonic.net + */ +public class NewtonsCradle + extends PhysicsDemo + implements DebugInitListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(NewtonsCradle.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = NewtonsCradle.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed at the bottom of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * proposed display settings, for the DsEditOverlay + */ + private static DisplaySettings proposedSettings; + /** + * temporary storage used in updateStatusText() + */ + final private static Vector3f tmpVector = new Vector3f(); + // ************************************************************************* + // constructors + + /** + * Instantiate the NewtonsCradle application. + */ + public NewtonsCradle() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the NewtonsCradle application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + for (String arg : arguments) { + switch (arg) { + case "--deleteOnly": + Heart.deleteStoredSettings(applicationName); + return; + default: + } + } + + NewtonsCradle application = new NewtonsCradle(); + RectSizeLimits sizeLimits = new RectSizeLimits( + 480, 240, // min width, height + 2_048, 1_080 // max width, height + ); + final String title = applicationName + " " + MyString.join(arguments); + proposedSettings = new DisplaySettings( + application, applicationName, sizeLimits) { + @Override + protected void applyOverrides(AppSettings settings) { + setShowDialog(ShowDialog.Never); + settings.setAudioRenderer(null); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + if (settings.getSamples() < 1) { + settings.setSamples(4); // anti-aliasing + } + settings.setResizable(true); + settings.setTitle(title); // Customize the window's title bar. + } + }; + + AppSettings appSettings = proposedSettings.initialize(); + if (appSettings != null) { + application.setSettings(appSettings); + /* + * If the settings dialog should be shown, + * it has already been shown by DisplaySettings.initialize(). + */ + application.setShowSettings(false); + + application.start(); + } + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + statusText.setLocalTranslation(205f, 25f, 0f); + guiNode.attachChild(statusText); + + AppState dseOverlay = new DsEditOverlay(proposedSettings); + boolean success = stateManager.attach(dseOverlay); + assert success; + + super.acorusInit(); + + restartSimulation(5); + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + ColorRGBA black = new ColorRGBA(0.01f, 0.01f, 0.01f, 1f); + Material ball = MyAsset.createShinyMaterial(assetManager, black); + ball.setFloat("Shininess", 100f); + registerMaterial("ball", ball); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of debug axis arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 12f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind("simulate 1", KeyInput.KEY_F1, KeyInput.KEY_1, + KeyInput.KEY_NUMPAD1); + dim.bind("simulate 2", KeyInput.KEY_F2, KeyInput.KEY_2, + KeyInput.KEY_NUMPAD2); + dim.bind("simulate 3", KeyInput.KEY_F3, KeyInput.KEY_3, + KeyInput.KEY_NUMPAD3); + dim.bind("simulate 4", KeyInput.KEY_F4, KeyInput.KEY_4, + KeyInput.KEY_NUMPAD4); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case asEditDisplaySettings: + InputMode editor = InputMode.findMode("dsEdit"); + InputMode.suspendAndActivate(editor); + return; + + case "simulate 1": + restartSimulation(1); + return; + case "simulate 2": + restartSimulation(2); + return; + case "simulate 3": + restartSimulation(3); + return; + case "simulate 4": + restartSimulation(4); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Callback invoked after the framebuffer is resized. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + proposedSettings.resize(newWidth, newHeight); + DsEditOverlay dseOverlay = stateManager.getState(DsEditOverlay.class); + dseOverlay.onViewPortResize(newWidth, newHeight); + + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusText(); + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private static void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootSpatial.addLight(sun); + sun.setName("sun"); + } + + /** + * Add a dynamic ball to the PhysicsSpace, suspended between 2 single-ended + * point-to-point joints. + * + * @param xOffset the desired X coordinate for the ball's center + * @return the new instance (not null) + */ + private PhysicsRigidBody addSuspendedBall(float xOffset) { + float radius = 9.9f; + SphereCollisionShape sphere = new SphereCollisionShape(radius); + PhysicsRigidBody ball = new PhysicsRigidBody(sphere); + Vector3f location = new Vector3f(xOffset, 0f, 0f); + Material material = findMaterial("ball"); + ball.setDebugMaterial(material); + ball.setDebugMeshNormals(MeshNormals.Sphere); + ball.setDebugMeshResolution(DebugShapeFactory.highResolution); + ball.setFriction(0f); + ball.setPhysicsLocation(location); + ball.setRestitution(1f); + addCollisionObject(ball); + + float wireLength = 80f; + float yOffset = wireLength * MyMath.rootHalf; + + Vector3f offset = new Vector3f(0f, yOffset, +yOffset); + Point2PointJoint joint1 = new Point2PointJoint(ball, offset); + addJoint(joint1); + + offset.set(0f, yOffset, -yOffset); + Point2PointJoint joint2 = new Point2PointJoint(ball, offset); + addJoint(joint2); + + return ball; + } + + /** + * Remove all physics objects from the PhysicsSpace. + */ + private void clearSpace() { + stateManager.detach(bulletAppState); + configurePhysics(); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(40f); + flyCam.setZoomSpeed(40f); + + cam.setLocation(new Vector3f(72f, 35f, 140f)); + cam.setRotation(new Quaternion(0.001f, 0.96926f, -0.031f, -0.244f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugInitListener(this); + stateManager.attach(bulletAppState); + + getPhysicsSpace().setAccuracy(0.01f); + setGravityAll(150f); + } + + /** + * Restart the simulation (paused) with the specified number of balls. + * + * @param numBalls (≥1) + */ + private void restartSimulation(int numBalls) { + clearSpace(); + this.speed = pausedSpeed; + + float xSeparation = 20f; + + // center-to-center separation between the first and last balls + float xExtent = (numBalls - 1) * xSeparation; + + float x0 = -xExtent / 2; + PhysicsRigidBody[] balls = new PhysicsRigidBody[numBalls]; + for (int ballIndex = 0; ballIndex < numBalls; ++ballIndex) { + float x = x0 + ballIndex * xSeparation; + balls[ballIndex] = addSuspendedBall(x); + } + + Vector3f kick = new Vector3f(-20f * numBalls, 0f, 0f); + balls[0].applyCentralImpulse(kick); + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + Vector3f gravity = physicsSpace.getGravity(null); + double totalEnergy = 0.0; + for (PhysicsRigidBody body : physicsSpace.getRigidBodyList()) { + totalEnergy += body.kineticEnergy(); + + body.getPhysicsLocation(tmpVector); + double potentialEnergy = -MyVector3f.dot(tmpVector, gravity); + totalEnergy += potentialEnergy; + } + + String message = String.format( + "KE+PE=%f%s", totalEnergy, isPaused() ? " PAUSED" : ""); + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/Pachinko.java b/MinieExamples/src/main/java/jme3utilities/minie/test/Pachinko.java index 556328e6b..de4095bee 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/Pachinko.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/Pachinko.java @@ -1,566 +1,566 @@ -/* - Copyright (c) 2022-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.RectSizeLimits; -import jme3utilities.math.noise.Generator; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.DisplaySettings; -import jme3utilities.ui.DsEditOverlay; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.ShowDialog; - -/** - * A physics demo that simulates a simple Pachinko machine. - *

- * https://en.wikipedia.org/wiki/Pachinko - * - * @author Stephen Gold sgold@sonic.net - */ -public class Pachinko - extends PhysicsDemo - implements DebugInitListener, PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * time interval between balls (in simulated seconds) - */ - final private static float addInterval = 3f; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(Pachinko.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = Pachinko.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed at the bottom of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * collision shape for balls - */ - private static CollisionShape ballShape; - /** - * proposed display settings, for the DsEditOverlay - */ - private static DisplaySettings proposedSettings; - /** - * elapsed time since a ball was added (in simulated seconds) - */ - private static float timeSinceAdded; - /** - * randomize initial motion of balls - */ - final private static Generator generator = new Generator(); - /** - * rotation matrix for pins - */ - final private static Matrix3f rot45 = new Matrix3f(); - // ************************************************************************* - // constructors - - /** - * Instantiate the Pachinko application. - */ - public Pachinko() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the Pachinko application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - for (String arg : arguments) { - switch (arg) { - case "--deleteOnly": - Heart.deleteStoredSettings(applicationName); - return; - default: - } - } - - Pachinko application = new Pachinko(); - RectSizeLimits sizeLimits = new RectSizeLimits( - 480, 240, // min width, height - 2_048, 1_080 // max width, height - ); - final String title = applicationName + " " + MyString.join(arguments); - proposedSettings = new DisplaySettings( - application, applicationName, sizeLimits) { - @Override - protected void applyOverrides(AppSettings settings) { - setShowDialog(ShowDialog.Never); - settings.setAudioRenderer(null); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - if (settings.getSamples() < 1) { - settings.setSamples(4); // anti-aliasing - } - settings.setResizable(true); - settings.setTitle(title); // Customize the window's title bar. - } - }; - - AppSettings appSettings = proposedSettings.initialize(); - if (appSettings != null) { - application.setSettings(appSettings); - /* - * If the settings dialog should be shown, - * it has already been shown by DisplaySettings.initialize(). - */ - application.setShowSettings(false); - - application.start(); - } - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - Quaternion q = new Quaternion().fromAngles(0f, 0f, FastMath.PI / 4f); - rot45.set(q); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - ColorRGBA skyColor = new ColorRGBA(0f, 0.1f, 0f, 1f); - viewPort.setBackgroundColor(skyColor); - - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - statusText.setLocalTranslation(205f, 25f, 0f); - guiNode.attachChild(statusText); - - AppState dseOverlay = new DsEditOverlay(proposedSettings); - boolean success = stateManager.attach(dseOverlay); - assert success; - - super.acorusInit(); - - float ballRadius = 1f; - ballShape = new SphereCollisionShape(ballRadius); - - restartSimulation(7); - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - ColorRGBA black = new ColorRGBA(0.01f, 0.01f, 0.01f, 1f); - Material ball = MyAsset.createShinyMaterial(assetManager, black); - ball.setFloat("Shininess", 100f); - registerMaterial("ball", ball); - - Material staticMaterial = MyAsset.createShadedMaterial( - assetManager, ColorRGBA.White.clone()); - registerMaterial("static", staticMaterial); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of debug axis arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind("simulate 4", KeyInput.KEY_F4, KeyInput.KEY_4, - KeyInput.KEY_NUMPAD4); - dim.bind("simulate 5", KeyInput.KEY_F5, KeyInput.KEY_5, - KeyInput.KEY_NUMPAD5); - dim.bind("simulate 6", KeyInput.KEY_F6, KeyInput.KEY_6, - KeyInput.KEY_NUMPAD6); - dim.bind("simulate 7", KeyInput.KEY_F7, KeyInput.KEY_7, - KeyInput.KEY_NUMPAD7); - dim.bind("simulate 8", KeyInput.KEY_F8, KeyInput.KEY_8, - KeyInput.KEY_NUMPAD8); - dim.bind("simulate 9", KeyInput.KEY_F9, KeyInput.KEY_9, - KeyInput.KEY_NUMPAD9); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - dim.bind(asToggleWArrows, KeyInput.KEY_N); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case asEditDisplaySettings: - InputMode editor = InputMode.findMode("dsEdit"); - InputMode.suspendAndActivate(editor); - return; - - case "simulate 1": - restartSimulation(1); - return; - case "simulate 2": - restartSimulation(2); - return; - case "simulate 3": - restartSimulation(3); - return; - case "simulate 4": - restartSimulation(4); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Callback invoked after the framebuffer is resized. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - proposedSettings.resize(newWidth, newHeight); - DsEditOverlay dseOverlay = stateManager.getState(DsEditOverlay.class); - dseOverlay.onViewPortResize(newWidth, newHeight); - - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusText(); - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - if (timeSinceAdded >= addInterval) { - addBall(); - timeSinceAdded = 0; - } - timeSinceAdded += timeStep; - } - // ************************************************************************* - // private methods - - /** - * Add a dynamic ball to the PhysicsSpace. - */ - private void addBall() { - PhysicsRigidBody result = new PhysicsRigidBody(ballShape); - Material material = findMaterial("ball"); - result.setDebugMaterial(material); - result.setDebugMeshNormals(MeshNormals.Sphere); - result.setDebugMeshResolution(DebugShapeFactory.highResolution); - addCollisionObject(result); - - result.setAngularDamping(0.9f); - result.setEnableSleep(false); - result.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - result.setRestitution(0.4f); - - // Restrict the ball's motion to the X-Y plane. - result.setAngularFactor(new Vector3f(0f, 0f, 1f)); - result.setLinearFactor(new Vector3f(1f, 1f, 0f)); - - // Apply a random horizontal impulse. - float xImpulse = (1f - 2f * generator.nextFloat()); - result.applyCentralImpulse(new Vector3f(xImpulse, 0f, 0f)); - } - - /** - * Add lighting to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private static void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootSpatial.addLight(sun); - sun.setName("sun"); - } - - /** - * Remove all physics objects from the PhysicsSpace. - */ - private void clearSpace() { - stateManager.detach(bulletAppState); - configurePhysics(); - } - - /** - * Configure the Camera and CIP during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(40f); - flyCam.setZoomSpeed(40f); - - cam.setLocation(new Vector3f(0f, -23f, 83f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugInitListener(this); - stateManager.attach(bulletAppState); - - PhysicsSpace space = bulletAppState.getPhysicsSpace(); - space.addTickListener(this); - } - - /** - * Restart the simulation with the specified number of rows of pins. - * - * @param numRows (≥1, ≤10) - */ - private void restartSimulation(int numRows) { - assert numRows > 0 && numRows <= 10 : numRows; - - getPhysicsSpace().destroy(); - - // Estimate the number of child shapes in the playing field. - int estNumChildren = numRows * (numRows + 1) + 3; - CompoundCollisionShape fieldShape - = new CompoundCollisionShape(estNumChildren); - - float barHalfWidth = 0.3f; - int lastRow = numRows - 1; - Vector3f tmpOffset = new Vector3f(); - - // Add child shapes for the pins. - float pinHalfHeight = 1f; - float pinHalfWidth = MyMath.rootHalf * barHalfWidth; - BoxCollisionShape pinShape = new BoxCollisionShape( - pinHalfWidth, pinHalfWidth, pinHalfHeight); - - float ballRadius = ballShape.maxRadius(); - float pinSpacing = 2f * (barHalfWidth + ballRadius); - float rowSpacing = 2f * pinSpacing; - - for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { - float y = -rowSpacing * rowIndex; - int numPins = numRows - (rowIndex % 2); - if (rowIndex == lastRow) { - numPins += 2; - } - for (int pinIndex = 0; pinIndex < numPins; ++pinIndex) { - float x = pinSpacing * (pinIndex - (numPins - 1) / 2f); - tmpOffset.set(x, y, 0f); - fieldShape.addChildShape(pinShape, tmpOffset, rot45); - } - } - - // Add child shapes for the vertical bars. - float barHalfLength = 0.5f * rowSpacing * (11 - numRows); - BoxCollisionShape barShape = new BoxCollisionShape( - barHalfWidth, barHalfLength, pinHalfHeight); - int numBars = numRows - (lastRow % 2) + 2; - float yBar = -rowSpacing * lastRow - barHalfLength; - - for (int barIndex = 0; barIndex < numBars; ++barIndex) { - float x = pinSpacing * (barIndex - (numBars - 1) / 2f); - fieldShape.addChildShape(barShape, x, yBar, 0f); - } - - // Add a child shape for the horizontal stop at the bottom. - float yStop = yBar - barHalfLength; - float stopHalfWidth = pinSpacing * (numBars - 1) / 2f + barHalfWidth; - BoxCollisionShape stopShape = new BoxCollisionShape( - stopHalfWidth, barHalfWidth, pinHalfHeight); - fieldShape.addChildShape(stopShape, 0f, yStop, 0f); - - PhysicsRigidBody playingField - = new PhysicsRigidBody(fieldShape, PhysicsBody.massForStatic); - playingField.setRestitution(0.7f); - Material staticMaterial = findMaterial("static"); - playingField.setDebugMaterial(staticMaterial); - - addCollisionObject(playingField); - timeSinceAdded = addInterval; - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - String message = isPaused() ? " PAUSED" : ""; - statusText.setText(message); - } -} +/* + Copyright (c) 2022-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.RectSizeLimits; +import jme3utilities.math.noise.Generator; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.DisplaySettings; +import jme3utilities.ui.DsEditOverlay; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.ShowDialog; + +/** + * A physics demo that simulates a simple Pachinko machine. + *

+ * https://en.wikipedia.org/wiki/Pachinko + * + * @author Stephen Gold sgold@sonic.net + */ +public class Pachinko + extends PhysicsDemo + implements DebugInitListener, PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * time interval between balls (in simulated seconds) + */ + final private static float addInterval = 3f; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(Pachinko.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = Pachinko.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed at the bottom of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * collision shape for balls + */ + private static CollisionShape ballShape; + /** + * proposed display settings, for the DsEditOverlay + */ + private static DisplaySettings proposedSettings; + /** + * elapsed time since a ball was added (in simulated seconds) + */ + private static float timeSinceAdded; + /** + * randomize initial motion of balls + */ + final private static Generator generator = new Generator(); + /** + * rotation matrix for pins + */ + final private static Matrix3f rot45 = new Matrix3f(); + // ************************************************************************* + // constructors + + /** + * Instantiate the Pachinko application. + */ + public Pachinko() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the Pachinko application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + for (String arg : arguments) { + switch (arg) { + case "--deleteOnly": + Heart.deleteStoredSettings(applicationName); + return; + default: + } + } + + Pachinko application = new Pachinko(); + RectSizeLimits sizeLimits = new RectSizeLimits( + 480, 240, // min width, height + 2_048, 1_080 // max width, height + ); + final String title = applicationName + " " + MyString.join(arguments); + proposedSettings = new DisplaySettings( + application, applicationName, sizeLimits) { + @Override + protected void applyOverrides(AppSettings settings) { + setShowDialog(ShowDialog.Never); + settings.setAudioRenderer(null); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + if (settings.getSamples() < 1) { + settings.setSamples(4); // anti-aliasing + } + settings.setResizable(true); + settings.setTitle(title); // Customize the window's title bar. + } + }; + + AppSettings appSettings = proposedSettings.initialize(); + if (appSettings != null) { + application.setSettings(appSettings); + /* + * If the settings dialog should be shown, + * it has already been shown by DisplaySettings.initialize(). + */ + application.setShowSettings(false); + + application.start(); + } + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + Quaternion q = new Quaternion().fromAngles(0f, 0f, FastMath.PI / 4f); + rot45.set(q); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + ColorRGBA skyColor = new ColorRGBA(0f, 0.1f, 0f, 1f); + viewPort.setBackgroundColor(skyColor); + + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + statusText.setLocalTranslation(205f, 25f, 0f); + guiNode.attachChild(statusText); + + AppState dseOverlay = new DsEditOverlay(proposedSettings); + boolean success = stateManager.attach(dseOverlay); + assert success; + + super.acorusInit(); + + float ballRadius = 1f; + ballShape = new SphereCollisionShape(ballRadius); + + restartSimulation(7); + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + ColorRGBA black = new ColorRGBA(0.01f, 0.01f, 0.01f, 1f); + Material ball = MyAsset.createShinyMaterial(assetManager, black); + ball.setFloat("Shininess", 100f); + registerMaterial("ball", ball); + + Material staticMaterial = MyAsset.createShadedMaterial( + assetManager, ColorRGBA.White.clone()); + registerMaterial("static", staticMaterial); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of debug axis arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind("simulate 4", KeyInput.KEY_F4, KeyInput.KEY_4, + KeyInput.KEY_NUMPAD4); + dim.bind("simulate 5", KeyInput.KEY_F5, KeyInput.KEY_5, + KeyInput.KEY_NUMPAD5); + dim.bind("simulate 6", KeyInput.KEY_F6, KeyInput.KEY_6, + KeyInput.KEY_NUMPAD6); + dim.bind("simulate 7", KeyInput.KEY_F7, KeyInput.KEY_7, + KeyInput.KEY_NUMPAD7); + dim.bind("simulate 8", KeyInput.KEY_F8, KeyInput.KEY_8, + KeyInput.KEY_NUMPAD8); + dim.bind("simulate 9", KeyInput.KEY_F9, KeyInput.KEY_9, + KeyInput.KEY_NUMPAD9); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + dim.bind(asToggleWArrows, KeyInput.KEY_N); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case asEditDisplaySettings: + InputMode editor = InputMode.findMode("dsEdit"); + InputMode.suspendAndActivate(editor); + return; + + case "simulate 1": + restartSimulation(1); + return; + case "simulate 2": + restartSimulation(2); + return; + case "simulate 3": + restartSimulation(3); + return; + case "simulate 4": + restartSimulation(4); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Callback invoked after the framebuffer is resized. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + proposedSettings.resize(newWidth, newHeight); + DsEditOverlay dseOverlay = stateManager.getState(DsEditOverlay.class); + dseOverlay.onViewPortResize(newWidth, newHeight); + + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusText(); + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + if (timeSinceAdded >= addInterval) { + addBall(); + timeSinceAdded = 0; + } + timeSinceAdded += timeStep; + } + // ************************************************************************* + // private methods + + /** + * Add a dynamic ball to the PhysicsSpace. + */ + private void addBall() { + PhysicsRigidBody result = new PhysicsRigidBody(ballShape); + Material material = findMaterial("ball"); + result.setDebugMaterial(material); + result.setDebugMeshNormals(MeshNormals.Sphere); + result.setDebugMeshResolution(DebugShapeFactory.highResolution); + addCollisionObject(result); + + result.setAngularDamping(0.9f); + result.setEnableSleep(false); + result.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + result.setRestitution(0.4f); + + // Restrict the ball's motion to the X-Y plane. + result.setAngularFactor(new Vector3f(0f, 0f, 1f)); + result.setLinearFactor(new Vector3f(1f, 1f, 0f)); + + // Apply a random horizontal impulse. + float xImpulse = (1f - 2f * generator.nextFloat()); + result.applyCentralImpulse(new Vector3f(xImpulse, 0f, 0f)); + } + + /** + * Add lighting to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private static void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootSpatial.addLight(sun); + sun.setName("sun"); + } + + /** + * Remove all physics objects from the PhysicsSpace. + */ + private void clearSpace() { + stateManager.detach(bulletAppState); + configurePhysics(); + } + + /** + * Configure the Camera and CIP during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(40f); + flyCam.setZoomSpeed(40f); + + cam.setLocation(new Vector3f(0f, -23f, 83f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugInitListener(this); + stateManager.attach(bulletAppState); + + PhysicsSpace space = bulletAppState.getPhysicsSpace(); + space.addTickListener(this); + } + + /** + * Restart the simulation with the specified number of rows of pins. + * + * @param numRows (≥1, ≤10) + */ + private void restartSimulation(int numRows) { + assert numRows > 0 && numRows <= 10 : numRows; + + getPhysicsSpace().destroy(); + + // Estimate the number of child shapes in the playing field. + int estNumChildren = numRows * (numRows + 1) + 3; + CompoundCollisionShape fieldShape + = new CompoundCollisionShape(estNumChildren); + + float barHalfWidth = 0.3f; + int lastRow = numRows - 1; + Vector3f tmpOffset = new Vector3f(); + + // Add child shapes for the pins. + float pinHalfHeight = 1f; + float pinHalfWidth = MyMath.rootHalf * barHalfWidth; + BoxCollisionShape pinShape = new BoxCollisionShape( + pinHalfWidth, pinHalfWidth, pinHalfHeight); + + float ballRadius = ballShape.maxRadius(); + float pinSpacing = 2f * (barHalfWidth + ballRadius); + float rowSpacing = 2f * pinSpacing; + + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + float y = -rowSpacing * rowIndex; + int numPins = numRows - (rowIndex % 2); + if (rowIndex == lastRow) { + numPins += 2; + } + for (int pinIndex = 0; pinIndex < numPins; ++pinIndex) { + float x = pinSpacing * (pinIndex - (numPins - 1) / 2f); + tmpOffset.set(x, y, 0f); + fieldShape.addChildShape(pinShape, tmpOffset, rot45); + } + } + + // Add child shapes for the vertical bars. + float barHalfLength = 0.5f * rowSpacing * (11 - numRows); + BoxCollisionShape barShape = new BoxCollisionShape( + barHalfWidth, barHalfLength, pinHalfHeight); + int numBars = numRows - (lastRow % 2) + 2; + float yBar = -rowSpacing * lastRow - barHalfLength; + + for (int barIndex = 0; barIndex < numBars; ++barIndex) { + float x = pinSpacing * (barIndex - (numBars - 1) / 2f); + fieldShape.addChildShape(barShape, x, yBar, 0f); + } + + // Add a child shape for the horizontal stop at the bottom. + float yStop = yBar - barHalfLength; + float stopHalfWidth = pinSpacing * (numBars - 1) / 2f + barHalfWidth; + BoxCollisionShape stopShape = new BoxCollisionShape( + stopHalfWidth, barHalfWidth, pinHalfHeight); + fieldShape.addChildShape(stopShape, 0f, yStop, 0f); + + PhysicsRigidBody playingField + = new PhysicsRigidBody(fieldShape, PhysicsBody.massForStatic); + playingField.setRestitution(0.7f); + Material staticMaterial = findMaterial("static"); + playingField.setDebugMaterial(staticMaterial); + + addCollisionObject(playingField); + timeSinceAdded = addInterval; + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + String message = isPaused() ? " PAUSED" : ""; + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/PoolDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/PoolDemo.java index bd3722cef..20599f963 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/PoolDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/PoolDemo.java @@ -1,707 +1,707 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.PointLight; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Sphere; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.shadow.PointLightShadowRenderer; -import com.jme3.system.AppSettings; -import com.jme3.texture.Texture; -import java.util.List; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.noise.Generator; -import jme3utilities.math.noise.Permutation; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.mesh.PoolHalfCushions; -import jme3utilities.minie.test.mesh.PoolTableSlice; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; - -/** - * Test/demonstrate 3 kinds of friction. - * - * TODO set cue ball after a scratch, provide aiming hints - * - * @author Stephen Gold sgold@sonic.net - */ -public class PoolDemo extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * radius of each ball (in physics-space units) - */ - final private static float ballRadius = 11.2f; - /** - * physics-space Y coordinate for the top surface of the platform - */ - final private static float platformTopY = 0f; - /** - * ID of the cue ball - */ - final private static int cueBallId = 0; - /** - * how many numbered balls - */ - final private static int numberedBalls = 15; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(PoolDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = PoolDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed in the upper-left corner of the GUI node - */ - final private static BitmapText[] statusLines = new BitmapText[1]; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * Mesh shared by all balls in the scene - */ - private static Mesh ballMesh; - /** - * scene-graph node to parent all spatials with physics controls - */ - final private static Node rbcNode = new Node("rbc node"); - // ************************************************************************* - // constructors - - /** - * Instantiate the PoolDemo application. - */ - public PoolDemo() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the PoolDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new PoolDemo(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - addStatusLines(); - super.acorusInit(); - - rootNode.attachChild(rbcNode); - addLighting(rootNode); - configureCamera(); - configureDumper(); - configurePhysics(); - generateMaterials(); - generateShapes(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - ColorRGBA bgColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - viewPort.setBackgroundColor(bgColor); - - restartScenario(); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - PhysicsDumper dumper = getDumper(); - - dumper.setEnabled(DumpFlags.MatParams, true); - //dumper.setEnabled(DumpFlags.ShadowModes, true); - //dumper.setEnabled(DumpFlags.Transforms, true); - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - // Change the platform material to double-sided. - Material platformMaterial = findMaterial("platform"); - RenderState ars = platformMaterial.getAdditionalRenderState(); - ars.setFaceCullMode(RenderState.FaceCullMode.Off); - /* - * Register a material for each numbered ball, based on textures - * generated by the MakePoolBalls application. - */ - final float shininess = 99f; - for (int ballId = 1; ballId <= numberedBalls; ++ballId) { - String ballName = ballName(ballId); - String assetPath = "Textures/poolBalls/" + ballName + ".png"; - final boolean generateMips = false; - Texture texture = MyAsset.loadTexture( - assetManager, assetPath, generateMips); - - Material material - = MyAsset.createShadedMaterial(assetManager, texture); - material.setColor("Ambient", new ColorRGBA(1f, 1f, 1f, 1f)); - material.setColor("Diffuse", new ColorRGBA(1f, 1f, 1f, 1f)); - material.setFloat("Shininess", shininess); - material.setColor("Specular", new ColorRGBA(1f, 1f, 1f, 1f)); - material.setBoolean("UseMaterialColors", true); - - registerMaterial(ballName, material); - } - - Material material - = MyAsset.createShinyMaterial(assetManager, ColorRGBA.White); - material.setFloat("Shininess", shininess); - registerMaterial(ballName(0), material); - } - - /** - * Initialize the library of named collision shapes during startup. - */ - @Override - public void generateShapes() { - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - registerShape("ball", ballShape); - - final int zSamples = 20; - final int radialSamples = 2 * zSamples; - ballMesh = new Sphere(zSamples, radialSamples, ballRadius); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of debug axis arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 20f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind("restart", KeyInput.KEY_NUMPAD5); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind("strike", "RMB"); - dim.bind("strike", KeyInput.KEY_RETURN, KeyInput.KEY_SPACE); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleDebug, KeyInput.KEY_SLASH); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind("toggle scene", KeyInput.KEY_M); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - dim.bind(asToggleWArrows, KeyInput.KEY_N); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "restart": - restartScenario(); - return; - - case "strike": - strikeBall(); - return; - - case "toggle scene": - toggleScene(); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusLines(); - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - Vector3f lightPosition = new Vector3f(0f, platformTopY + 600f, 0f); - ColorRGBA pointColor = new ColorRGBA(90f, 90f, 90f, 1f); - PointLight lamp = new PointLight(lightPosition, pointColor); - rootSpatial.addLight(lamp); - lamp.setName("lamp"); - lamp.setRadius(3_000f); - - viewPort.clearProcessors(); - int mapSize = 2_048; // in pixels - PointLightShadowRenderer shadowRenderer - = new PointLightShadowRenderer(assetManager, mapSize); - viewPort.addProcessor(shadowRenderer); - - shadowRenderer.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - shadowRenderer.setEdgesThickness(7); - shadowRenderer.setLight(lamp); - shadowRenderer.setShadowIntensity(0.7f); - } - - /** - * Add status lines to the GUI. - */ - private void addStatusLines() { - for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - guiNode.attachChild(statusLines[lineIndex]); - } - } - - /** - * Generate the name of the identified pool ball. - * - * @param ballId which ball (0=cue ball, 8=eight ball, ≥0, - * ≤numberedBalls) - * @return the name - */ - private static String ballName(int ballId) { - String result; - if (ballId == cueBallId) { - result = "cue"; - } else { - result = Integer.toString(ballId); - } - result += "Ball"; - - return result; - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 1f; - float far = 10_000f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(200f); - - cam.setLocation(new Vector3f(-44f, platformTopY + 189f, 425f)); - cam.setRotation(new Quaternion(0.003f, 0.971321f, -0.2374f, 0.0123f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.setAccuracy(0.001f); - physicsSpace.setGravity(new Vector3f(0f, -3_800f, 0f)); - physicsSpace.setMaxSubSteps(25); - } - - /** - * Restart the scenario. - */ - private void restartScenario() { - List children = rbcNode.getChildren(); - for (Spatial child : children) { - RigidBodyControl rbc = child.getControl(RigidBodyControl.class); - rbc.setPhysicsSpace(null); - } - rbcNode.detachAllChildren(); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - assert physicsSpace.isEmpty(); - - setUpTable(); - Vector3f location = new Vector3f(0f, platformTopY + ballRadius, 99f); - setUpBall(location, cueBallId); - setUpBallWedge(); - } - - /** - * Set up a single billiard ball. - * - * @param location the desired location (in physics-space coordinates, not - * null, unaffected) - * @param ballId which ball (0=cue ball, 8=eight ball, ≥0, - * ≤numberedBalls) - */ - private void setUpBall(Vector3f location, int ballId) { - String ballName = ballName(ballId); - Geometry geometry = new Geometry(ballName, ballMesh); - rbcNode.attachChild(geometry); - - geometry.setShadowMode(RenderQueue.ShadowMode.Cast); - - Material material = findMaterial(ballName); - assert material != null; - geometry.setMaterial(material); - - CollisionShape shape = findShape("ball"); - final float mass = 1f; - RigidBodyControl rbc = new RigidBodyControl(shape, mass); - geometry.addControl(rbc); - - rbc.setApplicationData(ballId); - rbc.setCcdMotionThreshold(ballRadius); - rbc.setCcdSweptSphereRadius(ballRadius); - rbc.setFriction(0.03f); - rbc.setPhysicsLocation(location); - - Generator random = getGenerator(); - Quaternion rotation = random.nextQuaternion(null); - rbc.setPhysicsRotation(rotation); - - PhysicsSpace space = getPhysicsSpace(); - rbc.setPhysicsSpace(space); - - rbc.setRestitution(0.9f); - } - - /** - * Set up 15 numbered balls in a wedge, as if for a game of Pool. - */ - private void setUpBallWedge() { - final float xSpacing = 2.09f * ballRadius; // center-to-center - float zSpacing = xSpacing / FastMath.sqrt(1.5f); // center-to-center - final float z0 = -60f; - final int numRows = 5; - - Random random = getGenerator(); - Permutation permutation; - while (true) { - long seed = random.nextLong(); - permutation = new Permutation(numberedBalls, seed); - - boolean eightBallInCenter = (1 + permutation.permute(4) == 8); - boolean leftBackCornerIsStripe = (1 + permutation.permute(10) > 8); - boolean rightBackCornerIsStripe = (1 + permutation.permute(14) > 8); - boolean legalRack = eightBallInCenter - && (leftBackCornerIsStripe ^ rightBackCornerIsStripe); - if (legalRack) { - break; - } - } - - int ballIndex = 0; - Vector3f location = new Vector3f(0f, platformTopY + ballRadius, z0); - for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { - int numBallsInRow = rowIndex + 1; - location.x = -(numBallsInRow - 1) * xSpacing / 2f; - for (int j = 0; j < numBallsInRow; ++j) { - int ballId = 1 + permutation.permute(ballIndex); - setUpBall(location, ballId); - ++ballIndex; - location.x += xSpacing; - } - location.z -= zSpacing; - } - } - - /** - * Set up a pool table. - */ - private void setUpTable() { - final float legLength = 190f; - final float pocketRadius = 22f; - final int numSliceArcEdges = 4; - Mesh tableSlice - = new PoolTableSlice(legLength, pocketRadius, numSliceArcEdges); - - Material platformMaterial = findMaterial("platform"); - assert platformMaterial != null; - - Node platformNode = new Node("platform node"); - rbcNode.attachChild(platformNode); - - Transform tmpTransform = new Transform(); - Quaternion rotation = tmpTransform.getRotation(); // alias - - for (int offsetIndex = 0; offsetIndex < 2; ++offsetIndex) { - float zOffset = (2 * offsetIndex - 1) * legLength; - tmpTransform.setTranslation(0f, 0f, zOffset); - - for (int yRotIndex = 0; yRotIndex < 4; ++yRotIndex) { - int sliceIndex = yRotIndex + 4 * offsetIndex; - String name = "tableSlice." + sliceIndex; - float yAngle = yRotIndex * FastMath.HALF_PI; - rotation.fromAngles(0f, yAngle, 0f); - Geometry geometry = new Geometry(name, tableSlice); - - platformNode.attachChild(geometry); - geometry.setLocalTransform(tmpTransform); - geometry.setMaterial(platformMaterial); - geometry.setShadowMode(RenderQueue.ShadowMode.Receive); - } - } - - int numCushionArcEdges = 20; - final float bumperHeight = 20f; - Mesh halfCushions = new PoolHalfCushions( - legLength, pocketRadius, numCushionArcEdges, bumperHeight); - - for (int yRotIndex = 0; yRotIndex < 2; ++yRotIndex) { - String name = "halfCushions." + yRotIndex; - float yAngle = (0.5f + yRotIndex) * FastMath.PI; - rotation.fromAngles(0f, yAngle, 0f); - Geometry geometry = new Geometry(name, halfCushions); - - platformNode.attachChild(geometry); - geometry.setLocalRotation(rotation); - geometry.setMaterial(platformMaterial); - geometry.setShadowMode(RenderQueue.ShadowMode.Receive); - } - - CollisionShape shape - = CollisionShapeFactory.createMergedMeshShape(platformNode); - RigidBodyControl rbc - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - platformNode.addControl(rbc); - rbc.setDebugNumSides(2); - rbc.setFriction(5f); - - PhysicsSpace space = getPhysicsSpace(); - rbc.setPhysicsSpace(space); - - rbc.setRestitution(0.6f); - rbc.setRollingFriction(10f); - rbc.setSpinningFriction(50f); - } - - /** - * Strike a ball, determining the location and direction from the camera and - * mouse cursor. - */ - private void strikeBall() { - Vector2f screenXY = inputManager.getCursorPosition(); - Vector3f nearLocation - = cam.getWorldCoordinates(screenXY, MyCamera.nearZ); - Vector3f farLocation = cam.getWorldCoordinates(screenXY, MyCamera.farZ); - PhysicsSpace space = getPhysicsSpace(); - List hits - = space.rayTest(nearLocation, farLocation); - - for (PhysicsRayTestResult hit : hits) { - PhysicsCollisionObject pco = hit.getCollisionObject(); - Object appObject = pco.getApplicationData(); - if (appObject instanceof Integer && cueBallId == (int) appObject) { - Vector3f impulseVector = farLocation.subtract(nearLocation); - float impulseMagnitude = 1_000f; // TODO player-controlled - float factor = impulseMagnitude / impulseVector.length(); - impulseVector.multLocal(factor); - - float hitFraction = hit.getHitFraction(); - Vector3f location = MyVector3f.lerp( - hitFraction, nearLocation, farLocation, null); - Vector3f ballCenter = pco.getPhysicsLocation(null); - location.subtractLocal(ballCenter); - - PhysicsRigidBody body = (PhysicsRigidBody) pco; - body.applyImpulse(impulseVector, location); - - if (isPaused()) { - togglePause(); - } - return; - } - } - } - - /** - * Toggle mesh rendering on/off. - */ - private static void toggleScene() { - Spatial.CullHint hint = rbcNode.getLocalCullHint(); - if (hint == Spatial.CullHint.Inherit - || hint == Spatial.CullHint.Never) { - hint = Spatial.CullHint.Always; - } else if (hint == Spatial.CullHint.Always) { - hint = Spatial.CullHint.Never; - } - rbcNode.setCullHint(hint); - } - - /** - * Update the status lines in the GUI. - */ - private void updateStatusLines() { - String message = "View: "; - - Spatial.CullHint cull = rbcNode.getLocalCullHint(); - message += (cull == Spatial.CullHint.Always) ? "NOscene" : "Scene"; - - boolean debug = bulletAppState.isDebugEnabled(); - if (debug) { - message += "+" + describePhysicsDebugOptions(); - } - message += isPaused() ? " PAUSED" : ""; - statusLines[0].setText(message); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.shadow.PointLightShadowRenderer; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.noise.Generator; +import jme3utilities.math.noise.Permutation; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.mesh.PoolHalfCushions; +import jme3utilities.minie.test.mesh.PoolTableSlice; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; + +/** + * Test/demonstrate 3 kinds of friction. + * + * TODO set cue ball after a scratch, provide aiming hints + * + * @author Stephen Gold sgold@sonic.net + */ +public class PoolDemo extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * radius of each ball (in physics-space units) + */ + final private static float ballRadius = 11.2f; + /** + * physics-space Y coordinate for the top surface of the platform + */ + final private static float platformTopY = 0f; + /** + * ID of the cue ball + */ + final private static int cueBallId = 0; + /** + * how many numbered balls + */ + final private static int numberedBalls = 15; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(PoolDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = PoolDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed in the upper-left corner of the GUI node + */ + final private static BitmapText[] statusLines = new BitmapText[1]; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * Mesh shared by all balls in the scene + */ + private static Mesh ballMesh; + /** + * scene-graph node to parent all spatials with physics controls + */ + final private static Node rbcNode = new Node("rbc node"); + // ************************************************************************* + // constructors + + /** + * Instantiate the PoolDemo application. + */ + public PoolDemo() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the PoolDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new PoolDemo(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + addStatusLines(); + super.acorusInit(); + + rootNode.attachChild(rbcNode); + addLighting(rootNode); + configureCamera(); + configureDumper(); + configurePhysics(); + generateMaterials(); + generateShapes(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + ColorRGBA bgColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + viewPort.setBackgroundColor(bgColor); + + restartScenario(); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + PhysicsDumper dumper = getDumper(); + + dumper.setEnabled(DumpFlags.MatParams, true); + //dumper.setEnabled(DumpFlags.ShadowModes, true); + //dumper.setEnabled(DumpFlags.Transforms, true); + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + // Change the platform material to double-sided. + Material platformMaterial = findMaterial("platform"); + RenderState ars = platformMaterial.getAdditionalRenderState(); + ars.setFaceCullMode(RenderState.FaceCullMode.Off); + /* + * Register a material for each numbered ball, based on textures + * generated by the MakePoolBalls application. + */ + final float shininess = 99f; + for (int ballId = 1; ballId <= numberedBalls; ++ballId) { + String ballName = ballName(ballId); + String assetPath = "Textures/poolBalls/" + ballName + ".png"; + final boolean generateMips = false; + Texture texture = MyAsset.loadTexture( + assetManager, assetPath, generateMips); + + Material material + = MyAsset.createShadedMaterial(assetManager, texture); + material.setColor("Ambient", new ColorRGBA(1f, 1f, 1f, 1f)); + material.setColor("Diffuse", new ColorRGBA(1f, 1f, 1f, 1f)); + material.setFloat("Shininess", shininess); + material.setColor("Specular", new ColorRGBA(1f, 1f, 1f, 1f)); + material.setBoolean("UseMaterialColors", true); + + registerMaterial(ballName, material); + } + + Material material + = MyAsset.createShinyMaterial(assetManager, ColorRGBA.White); + material.setFloat("Shininess", shininess); + registerMaterial(ballName(0), material); + } + + /** + * Initialize the library of named collision shapes during startup. + */ + @Override + public void generateShapes() { + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + registerShape("ball", ballShape); + + final int zSamples = 20; + final int radialSamples = 2 * zSamples; + ballMesh = new Sphere(zSamples, radialSamples, ballRadius); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of debug axis arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 20f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind("restart", KeyInput.KEY_NUMPAD5); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind("strike", "RMB"); + dim.bind("strike", KeyInput.KEY_RETURN, KeyInput.KEY_SPACE); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleDebug, KeyInput.KEY_SLASH); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind("toggle scene", KeyInput.KEY_M); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + dim.bind(asToggleWArrows, KeyInput.KEY_N); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "restart": + restartScenario(); + return; + + case "strike": + strikeBall(); + return; + + case "toggle scene": + toggleScene(); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusLines(); + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + Vector3f lightPosition = new Vector3f(0f, platformTopY + 600f, 0f); + ColorRGBA pointColor = new ColorRGBA(90f, 90f, 90f, 1f); + PointLight lamp = new PointLight(lightPosition, pointColor); + rootSpatial.addLight(lamp); + lamp.setName("lamp"); + lamp.setRadius(3_000f); + + viewPort.clearProcessors(); + int mapSize = 2_048; // in pixels + PointLightShadowRenderer shadowRenderer + = new PointLightShadowRenderer(assetManager, mapSize); + viewPort.addProcessor(shadowRenderer); + + shadowRenderer.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + shadowRenderer.setEdgesThickness(7); + shadowRenderer.setLight(lamp); + shadowRenderer.setShadowIntensity(0.7f); + } + + /** + * Add status lines to the GUI. + */ + private void addStatusLines() { + for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + guiNode.attachChild(statusLines[lineIndex]); + } + } + + /** + * Generate the name of the identified pool ball. + * + * @param ballId which ball (0=cue ball, 8=eight ball, ≥0, + * ≤numberedBalls) + * @return the name + */ + private static String ballName(int ballId) { + String result; + if (ballId == cueBallId) { + result = "cue"; + } else { + result = Integer.toString(ballId); + } + result += "Ball"; + + return result; + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 1f; + float far = 10_000f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(200f); + + cam.setLocation(new Vector3f(-44f, platformTopY + 189f, 425f)); + cam.setRotation(new Quaternion(0.003f, 0.971321f, -0.2374f, 0.0123f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.setAccuracy(0.001f); + physicsSpace.setGravity(new Vector3f(0f, -3_800f, 0f)); + physicsSpace.setMaxSubSteps(25); + } + + /** + * Restart the scenario. + */ + private void restartScenario() { + List children = rbcNode.getChildren(); + for (Spatial child : children) { + RigidBodyControl rbc = child.getControl(RigidBodyControl.class); + rbc.setPhysicsSpace(null); + } + rbcNode.detachAllChildren(); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + assert physicsSpace.isEmpty(); + + setUpTable(); + Vector3f location = new Vector3f(0f, platformTopY + ballRadius, 99f); + setUpBall(location, cueBallId); + setUpBallWedge(); + } + + /** + * Set up a single billiard ball. + * + * @param location the desired location (in physics-space coordinates, not + * null, unaffected) + * @param ballId which ball (0=cue ball, 8=eight ball, ≥0, + * ≤numberedBalls) + */ + private void setUpBall(Vector3f location, int ballId) { + String ballName = ballName(ballId); + Geometry geometry = new Geometry(ballName, ballMesh); + rbcNode.attachChild(geometry); + + geometry.setShadowMode(RenderQueue.ShadowMode.Cast); + + Material material = findMaterial(ballName); + assert material != null; + geometry.setMaterial(material); + + CollisionShape shape = findShape("ball"); + final float mass = 1f; + RigidBodyControl rbc = new RigidBodyControl(shape, mass); + geometry.addControl(rbc); + + rbc.setApplicationData(ballId); + rbc.setCcdMotionThreshold(ballRadius); + rbc.setCcdSweptSphereRadius(ballRadius); + rbc.setFriction(0.03f); + rbc.setPhysicsLocation(location); + + Generator random = getGenerator(); + Quaternion rotation = random.nextQuaternion(null); + rbc.setPhysicsRotation(rotation); + + PhysicsSpace space = getPhysicsSpace(); + rbc.setPhysicsSpace(space); + + rbc.setRestitution(0.9f); + } + + /** + * Set up 15 numbered balls in a wedge, as if for a game of Pool. + */ + private void setUpBallWedge() { + final float xSpacing = 2.09f * ballRadius; // center-to-center + float zSpacing = xSpacing / FastMath.sqrt(1.5f); // center-to-center + final float z0 = -60f; + final int numRows = 5; + + Random random = getGenerator(); + Permutation permutation; + while (true) { + long seed = random.nextLong(); + permutation = new Permutation(numberedBalls, seed); + + boolean eightBallInCenter = (1 + permutation.permute(4) == 8); + boolean leftBackCornerIsStripe = (1 + permutation.permute(10) > 8); + boolean rightBackCornerIsStripe = (1 + permutation.permute(14) > 8); + boolean legalRack = eightBallInCenter + && (leftBackCornerIsStripe ^ rightBackCornerIsStripe); + if (legalRack) { + break; + } + } + + int ballIndex = 0; + Vector3f location = new Vector3f(0f, platformTopY + ballRadius, z0); + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + int numBallsInRow = rowIndex + 1; + location.x = -(numBallsInRow - 1) * xSpacing / 2f; + for (int j = 0; j < numBallsInRow; ++j) { + int ballId = 1 + permutation.permute(ballIndex); + setUpBall(location, ballId); + ++ballIndex; + location.x += xSpacing; + } + location.z -= zSpacing; + } + } + + /** + * Set up a pool table. + */ + private void setUpTable() { + final float legLength = 190f; + final float pocketRadius = 22f; + final int numSliceArcEdges = 4; + Mesh tableSlice + = new PoolTableSlice(legLength, pocketRadius, numSliceArcEdges); + + Material platformMaterial = findMaterial("platform"); + assert platformMaterial != null; + + Node platformNode = new Node("platform node"); + rbcNode.attachChild(platformNode); + + Transform tmpTransform = new Transform(); + Quaternion rotation = tmpTransform.getRotation(); // alias + + for (int offsetIndex = 0; offsetIndex < 2; ++offsetIndex) { + float zOffset = (2 * offsetIndex - 1) * legLength; + tmpTransform.setTranslation(0f, 0f, zOffset); + + for (int yRotIndex = 0; yRotIndex < 4; ++yRotIndex) { + int sliceIndex = yRotIndex + 4 * offsetIndex; + String name = "tableSlice." + sliceIndex; + float yAngle = yRotIndex * FastMath.HALF_PI; + rotation.fromAngles(0f, yAngle, 0f); + Geometry geometry = new Geometry(name, tableSlice); + + platformNode.attachChild(geometry); + geometry.setLocalTransform(tmpTransform); + geometry.setMaterial(platformMaterial); + geometry.setShadowMode(RenderQueue.ShadowMode.Receive); + } + } + + int numCushionArcEdges = 20; + final float bumperHeight = 20f; + Mesh halfCushions = new PoolHalfCushions( + legLength, pocketRadius, numCushionArcEdges, bumperHeight); + + for (int yRotIndex = 0; yRotIndex < 2; ++yRotIndex) { + String name = "halfCushions." + yRotIndex; + float yAngle = (0.5f + yRotIndex) * FastMath.PI; + rotation.fromAngles(0f, yAngle, 0f); + Geometry geometry = new Geometry(name, halfCushions); + + platformNode.attachChild(geometry); + geometry.setLocalRotation(rotation); + geometry.setMaterial(platformMaterial); + geometry.setShadowMode(RenderQueue.ShadowMode.Receive); + } + + CollisionShape shape + = CollisionShapeFactory.createMergedMeshShape(platformNode); + RigidBodyControl rbc + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + platformNode.addControl(rbc); + rbc.setDebugNumSides(2); + rbc.setFriction(5f); + + PhysicsSpace space = getPhysicsSpace(); + rbc.setPhysicsSpace(space); + + rbc.setRestitution(0.6f); + rbc.setRollingFriction(10f); + rbc.setSpinningFriction(50f); + } + + /** + * Strike a ball, determining the location and direction from the camera and + * mouse cursor. + */ + private void strikeBall() { + Vector2f screenXY = inputManager.getCursorPosition(); + Vector3f nearLocation + = cam.getWorldCoordinates(screenXY, MyCamera.nearZ); + Vector3f farLocation = cam.getWorldCoordinates(screenXY, MyCamera.farZ); + PhysicsSpace space = getPhysicsSpace(); + List hits + = space.rayTest(nearLocation, farLocation); + + for (PhysicsRayTestResult hit : hits) { + PhysicsCollisionObject pco = hit.getCollisionObject(); + Object appObject = pco.getApplicationData(); + if (appObject instanceof Integer && cueBallId == (int) appObject) { + Vector3f impulseVector = farLocation.subtract(nearLocation); + float impulseMagnitude = 1_000f; // TODO player-controlled + float factor = impulseMagnitude / impulseVector.length(); + impulseVector.multLocal(factor); + + float hitFraction = hit.getHitFraction(); + Vector3f location = MyVector3f.lerp( + hitFraction, nearLocation, farLocation, null); + Vector3f ballCenter = pco.getPhysicsLocation(null); + location.subtractLocal(ballCenter); + + PhysicsRigidBody body = (PhysicsRigidBody) pco; + body.applyImpulse(impulseVector, location); + + if (isPaused()) { + togglePause(); + } + return; + } + } + } + + /** + * Toggle mesh rendering on/off. + */ + private static void toggleScene() { + Spatial.CullHint hint = rbcNode.getLocalCullHint(); + if (hint == Spatial.CullHint.Inherit + || hint == Spatial.CullHint.Never) { + hint = Spatial.CullHint.Always; + } else if (hint == Spatial.CullHint.Always) { + hint = Spatial.CullHint.Never; + } + rbcNode.setCullHint(hint); + } + + /** + * Update the status lines in the GUI. + */ + private void updateStatusLines() { + String message = "View: "; + + Spatial.CullHint cull = rbcNode.getLocalCullHint(); + message += (cull == Spatial.CullHint.Always) ? "NOscene" : "Scene"; + + boolean debug = bulletAppState.isDebugEnabled(); + if (debug) { + message += "+" + describePhysicsDebugOptions(); + } + message += isPaused() ? " PAUSED" : ""; + statusLines[0].setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/RopeDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/RopeDemo.java index 304a64769..81af3a9a9 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/RopeDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/RopeDemo.java @@ -1,1135 +1,1135 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.anim.Armature; -import com.jme3.anim.Joint; -import com.jme3.anim.SkinningControl; -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DacConfiguration; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.IKJoint; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.bullet.joints.Constraint; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.system.AppSettings; -import java.util.ArrayDeque; -import java.util.BitSet; -import java.util.Deque; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MySkeleton; -import jme3utilities.MyString; -import jme3utilities.NameGenerator; -import jme3utilities.debug.SkeletonVisualizer; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.RectSizeLimits; -import jme3utilities.math.noise.Generator; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.mesh.TubeTreeMesh; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.DisplaySettings; -import jme3utilities.ui.DsEditOverlay; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.ShowDialog; - -/** - * Simulate ropes using DynamicAnimControl. - *

- * Seen in the February 2019 demo video: - * https://www.youtube.com/watch?v=7PYDAyB5RCE - * - * @author Stephen Gold sgold@sonic.net - */ -public class RopeDemo extends PhysicsDemo { - // ************************************************************************* - // classes and enums - - /** - * Enumerate the rope shapes used in this demo. - */ - private enum RopeShape { - /** - * a rope with 4 ends - */ - Cross, - /** - * a noose, pinned at the standing end - */ - Noose, - /** - * a noose, pinned at the standing end, kinematically pre-spliced - */ - NooseSpliced, - /** - * a rope ring - */ - Ring, - /** - * a rope ring, kinematically pre-spliced - */ - RingSpliced, - /** - * a plain rope with slack, pinned at both ends - */ - Slackline - } - // ************************************************************************* - // constants and loggers - - /** - * cross-section radius for ropes (in mesh units) - */ - final private static float ropeRadius = 0.4f; - /** - * minimum curl radius for ropes (in mesh units) - */ - final private static float minCurlRadius = 4f * ropeRadius; - /** - * mass of each link (in physics mass units) - */ - final private static float linkMass = 1e-6f; - /** - * length of each rope segment (in mesh units) - */ - final private static float stepLength = 1.9f * minCurlRadius; - /** - * number of mesh loops in each tube segment - */ - final private static int loopsPerSegment = 3; - /** - * number of sample points per mesh loop - */ - final private static int samplesPerLoop = 12; - /** - * link configuration for leaf joints (shrunken hull shape) - */ - final private static LinkConfig leafConfig = new LinkConfig( - linkMass, MassHeuristic.Mass, ShapeHeuristic.VertexHull, - new Vector3f(0.84f, 0.84f, 0.84f), CenterHeuristic.Mean); - /** - * link configuration for non-leaf joints (stretched capsule shape) - */ - final private static LinkConfig ropeConfig = new LinkConfig( - linkMass, MassHeuristic.Mass, ShapeHeuristic.TwoSphere, - new Vector3f(1f, 1f, 2.5f), CenterHeuristic.Mean); - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(RopeDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = RopeDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * true once {@link #initWhenReady()} has been invoked for the latest rope - */ - private static boolean dacReadyInitDone = false; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * physics controls for the ropes, in order of creation - */ - final private static Deque dacs = new ArrayDeque<>(12); - /** - * shapes of the ropes, in order of creation - */ - final private static Deque shapes = new ArrayDeque<>(12); - /** - * proposed display settings (for editing) - */ - private static DisplaySettings proposedSettings; - /** - * AppState to manage the display-settings editor - */ - private static DsEditOverlay dseOverlay; - /** - * generate names for rope geometries - */ - final private static NameGenerator geometryNamer = new NameGenerator(); - /** - * parent for rope geometries - */ - final private static Node meshesNode = new Node("meshes node"); - /** - * visualizer for the Armature of the most recently added rope - */ - private static SkeletonVisualizer sv; - // ************************************************************************* - // constructors - - /** - * Instantiate the RopeDemo application. - */ - public RopeDemo() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the RopeDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Process any command-line arguments. - ShowDialog showDialog = ShowDialog.Never; - for (String arg : arguments) { - switch (arg) { - case "--deleteOnly": - Heart.deleteStoredSettings(applicationName); - return; - - case "--showSettingsDialog": - showDialog = ShowDialog.FirstTime; - break; - - case "--verbose": - Heart.setLoggingLevels(Level.INFO); - break; - - default: - logger.log(Level.WARNING, - "Ignored unknown command-line argument {0}", - MyString.quote(arg)); - } - } - mainStartup(showDialog, title); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - dseOverlay = new DsEditOverlay(proposedSettings); - dseOverlay.setBackgroundColor(new ColorRGBA(0.05f, 0f, 0f, 1f)); - boolean success = stateManager.attach(dseOverlay); - assert success; - super.acorusInit(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - addLighting(); - - float halfExtent = 650f; - float topY = 0f; - attachCubePlatform(halfExtent, topY); - addSkeleton(); - - rootNode.attachChild(meshesNode); - } - - /** - * Configure the PhysicsDumper. - */ - @Override - public void configureDumper() { - super.configureDumper(); - - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.JointsInSpaces, true); - } - - /** - * Calculate screen bounds for a detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes on the right side of the viewport. - float margin = 10f; // in pixels - float height = viewPortHeight - (2f * margin); - float width = 260f; // in pixels - float leftX = viewPortWidth - (width + margin); - float topY = margin + height; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Initialize materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - ColorRGBA taupe = new ColorRGBA().setAsSrgb(0.6f, 0.5f, 0.4f, 1f); - Material ropeMaterial - = MyAsset.createShadedMaterial(assetManager, taupe); - registerMaterial("rope", ropeMaterial); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of debug axis arrows when visible. - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 0.5f; - } - - /** - * Add application-specific hotkey bindings and override existing ones. - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind("add Cross", KeyInput.KEY_F2); - dim.bind("add Ring", KeyInput.KEY_F3); - dim.bind("add RingSpliced", KeyInput.KEY_F4); - dim.bind("add Noose", KeyInput.KEY_F7); - dim.bind("add NooseSpliced", KeyInput.KEY_F8); - dim.bind("add Slackline", KeyInput.KEY_F1); - - dim.bind("delete", KeyInput.KEY_BACK, KeyInput.KEY_DELETE); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); - dim.bind("go limp", KeyInput.KEY_SPACE); - dim.bind("pull a pin", KeyInput.KEY_X); - dim.bind("save", KeyInput.KEY_SEMICOLON); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleDebug, KeyInput.KEY_SLASH); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind("toggle meshes", KeyInput.KEY_M); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind("toggle skeleton", KeyInput.KEY_V); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "delete": - delete(); - return; - - case asEditDisplaySettings: - activateInputMode("dsEdit"); - return; - - case "go limp": - goLimp(); - return; - - case "pull a pin": - pullAPin(); - return; - - case "toggle meshes": - toggleMeshes(); - return; - case "toggle skeleton": - toggleSkeleton(); - return; - - default: - } - - String[] words = actionString.split(" "); - if (words.length >= 2 && "add".equals(words[0])) { - addRope(words[1]); - return; - } - - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - dseOverlay.onViewPortResize(newWidth, newHeight); - proposedSettings.resize(newWidth, newHeight); - - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - DynamicAnimControl latestDac = dacs.peekLast(); - if (!dacReadyInitDone && latestDac != null && latestDac.isReady()) { - initWhenReady(); - dacReadyInitDone = true; - } - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the main scene. - */ - private void addLighting() { - ColorRGBA ambientColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootNode.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -2f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - sun.setName("sun"); - - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.5f); - viewPort.addProcessor(dlsr); - } - - /** - * Add a kinematic rope (with the named shape) to the scene. - * - * @param shapeName the name of the shape to add (not null, not empty) - */ - private void addRope(String shapeName) { - DynamicAnimControl latestDac = dacs.peekLast(); - if (latestDac != null && latestDac.getTorsoLink().isKinematic()) { - // only one kinematic rope at a time - delete(); - } - - RopeShape shape = RopeShape.valueOf(shapeName); - switch (shape) { - case Cross: - addRopeCross(); - break; - case Noose: - addRopeNoose(false); - break; - case NooseSpliced: - addRopeNoose(true); - break; - case Ring: - addRopeRing(false); - break; - case RingSpliced: - addRopeRing(true); - break; - case Slackline: - addRopeSlackline(); - break; - default: - throw new IllegalArgumentException(shapeName); - } - - shapes.addLast(shape); - } - - /** - * Add a rope cross to the scene. - */ - private void addRopeCross() { - // Generate a cross-shaped Armature. - int[] stepCounts = {5, 5, 5, 5}; - Vector3f[] stepOffsets = { - new Vector3f(stepLength, 0f, 0f), - new Vector3f(-stepLength, 0f, 0f), - new Vector3f(0f, 0f, stepLength), - new Vector3f(0f, 0f, -stepLength) - }; - Armature armature = makeArmature(stepCounts, stepOffsets); - - TubeTreeMesh ropeMesh = new TubeTreeMesh( - armature, ropeRadius, 0f, loopsPerSegment, samplesPerLoop); - - String geometryName = geometryNamer.unique("rope cross"); - Geometry geometry = new Geometry(geometryName, ropeMesh); - - addRopeSpatial(armature, geometry); - - // Curl all branches clockwise to introduce some slack. - curlBranch(armature, 0, Vector3f.UNIT_Y, 0.2f); - curlBranch(armature, 1, Vector3f.UNIT_Y, 0.2f); - curlBranch(armature, 2, Vector3f.UNIT_Y, 0.2f); - curlBranch(armature, 3, Vector3f.UNIT_Y, 0.2f); - } - - /** - * Add a noose to the scene. - * - * @param kinematicSplice true to pre-splice the ends kinematically, false - * to leave it Y-shaped until dynamic mode is set - */ - private void addRopeNoose(boolean kinematicSplice) { - // Generate a Y-shaped Armature. Branch0 forms the stem of the Y. - int[] stepCounts = {6, 6, 6}; - float dx = stepLength * MyMath.rootHalf; - Vector3f[] stepOffsets = { - new Vector3f(stepLength, 0f, 0f), - new Vector3f(-dx, 0f, -dx), - new Vector3f(-dx, 0f, dx) - }; - Armature armature = makeArmature(stepCounts, stepOffsets); - - TubeTreeMesh ropeMesh = new TubeTreeMesh( - armature, ropeRadius, 0f, loopsPerSegment, samplesPerLoop); - String geometryName = geometryNamer.unique("noose"); - Geometry geometry = new Geometry(geometryName, ropeMesh); - - addRopeSpatial(armature, geometry); - - if (kinematicSplice) { - // Curl branch1 and branch2 toward one another, 90 degrees each. - curlBranch(armature, 1, Vector3f.UNIT_Y, FastMath.HALF_PI); - curlBranch(armature, 2, Vector3f.UNIT_Y, -FastMath.HALF_PI); - } - } - - /** - * Add a rope ring to the scene. - * - * @param kinematicSplice true to pre-splice the ends kinematically, false - * to leave it linear until dynamic mode is set - */ - private void addRopeRing(boolean kinematicSplice) { - // Generate a double-ended straight-line Armature. - int[] stepCounts = {8, 8}; - Vector3f[] stepOffsets = { - new Vector3f(stepLength, 0f, 0f), - new Vector3f(-stepLength, 0f, 0f) - }; - Armature armature = makeArmature(stepCounts, stepOffsets); - - TubeTreeMesh ropeMesh = new TubeTreeMesh( - armature, ropeRadius, 0f, loopsPerSegment, samplesPerLoop); - - String geometryName = geometryNamer.unique("rope ring"); - Geometry geometry = new Geometry(geometryName, ropeMesh); - - addRopeSpatial(armature, geometry); - - if (kinematicSplice) { - // Curl branch0 and branch1 toward one another, 180 degrees each. - curlBranch(armature, 0, Vector3f.UNIT_Y, FastMath.PI); - curlBranch(armature, 1, Vector3f.UNIT_Y, -FastMath.PI); - } - } - - /** - * Add a plain rope to the scene. The rope begins in the X-Z plane. - */ - private void addRopeSlackline() { - // Generate a double-ended straight-line Armature. - int[] stepCounts = {8, 8}; - Vector3f[] stepOffsets = { - new Vector3f(0f, 0f, stepLength), - new Vector3f(0f, 0f, -stepLength) - }; - Armature armature = makeArmature(stepCounts, stepOffsets); - - float leafOvershoot = 0.8f * ropeRadius; - TubeTreeMesh ropeMesh = new TubeTreeMesh(armature, ropeRadius, - leafOvershoot, loopsPerSegment, samplesPerLoop); - String geometryName = geometryNamer.unique("slackline"); - Geometry geometry = new Geometry(geometryName, ropeMesh); - - addRopeSpatial(armature, geometry); - - // Curl both branches clockwise to introduce some slack. - curlBranch(armature, 0, Vector3f.UNIT_Y, 0.2f); - curlBranch(armature, 1, Vector3f.UNIT_Y, 0.2f); - } - - /** - * Code for adding a rope that's shared by all shapes. - * - * @param armature (not null) - * @param spatial (not null) - */ - private void addRopeSpatial(Armature armature, Spatial spatial) { - // Set a random elevation and Y-axis rotation. - Generator random = getGenerator(); - float elevation = random.nextFloat(12f, 24f); - float rotationAngle = random.nextFloat(0f, FastMath.TWO_PI); - spatial.move(0f, elevation, 0f); - spatial.rotate(0f, rotationAngle, 0f); - - spatial.setCullHint(Spatial.CullHint.Never); - Material ropeMaterial = findMaterial("rope"); - spatial.setMaterial(ropeMaterial); - spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - - SkinningControl sControl = new SkinningControl(armature); - spatial.addControl(sControl); - sControl.setHardwareSkinningPreferred(false); - sv.setSubject(sControl); - - assert stepLength < 2f * minCurlRadius : stepLength; - float maxAngle = FastMath.acos(stepLength / (2f * minCurlRadius)); - RangeOfMotion rom = new RangeOfMotion(maxAngle); - - DynamicAnimControl dac = new DynamicAnimControl(); - for (Joint joint : MySkeleton.preOrderJoints(armature)) { - if (joint.getParent() != null) { - String jointName = joint.getName(); - boolean isLeafJoint = joint.getChildren().isEmpty(); - LinkConfig linkConfig = isLeafJoint ? leafConfig : ropeConfig; - dac.link(jointName, linkConfig, rom); - } - } - dac.setConfig(DacConfiguration.torsoName, ropeConfig); - dac.setDamping(0.9f); - dac.setGravity(new Vector3f(0f, -120f, 0f)); - - spatial.addControl(dac); - PhysicsSpace physicsSpace = getPhysicsSpace(); - dac.setPhysicsSpace(physicsSpace); - meshesNode.attachChild(spatial); - - setFrictionAll(9e9f); - - dacs.addLast(dac); - dacReadyInitDone = false; - } - - /** - * Add the skeleton visualizer the scene. - */ - private void addSkeleton() { - sv = new SkeletonVisualizer(assetManager, null); - rootNode.addControl(sv); - sv.setLineColor(ColorRGBA.Red); - sv.setHeadColor(0, ColorRGBA.Green); - sv.setHeadSize(8); - } - - /** - * Choose 3 distinct vertices in the end cap of the indexed branch. - * - * @param branchIndex which branch (≥0) - * @return a new array of 3 vertex specifiers - */ - private static String[] capSpecs(int branchIndex) { - int capVertex0 = capVertex0(branchIndex); - String geometryName = dacs.peekLast().getSpatial().getName(); - String[] result = new String[3]; - result[0] = String.format("%d/%s", capVertex0, geometryName); - - if (samplesPerLoop == 6) { - result[1] = String.format("%d/%s", capVertex0 + 1, geometryName); - result[2] = String.format("%d/%s", capVertex0 + 7, geometryName); - } else if (samplesPerLoop == 9) { - result[1] = String.format("%d/%s", capVertex0 + 4, geometryName); - result[2] = String.format("%d/%s", capVertex0 + 13, geometryName); - } else if (samplesPerLoop == 12) { - result[1] = String.format("%d/%s", capVertex0 + 7, geometryName); - result[2] = String.format("%d/%s", capVertex0 + 19, geometryName); - } else { - String message = "samplesPerLoop = " + samplesPerLoop; - throw new IllegalStateException(message); - } - - return result; - } - - /** - * Find the index of the first vertex in the end cap of the indexed branch. - * - * @param branchIndex which branch(≥0) - * @return the vertex index (≥0) - */ - private static int capVertex0(int branchIndex) { - assert branchIndex >= 0 : branchIndex; - - DynamicAnimControl latestDac = dacs.peekLast(); - Armature armature = latestDac.getArmature(); - int lastStep = countSteps(armature, branchIndex) - 1; - String endJointName = jointName(branchIndex, lastStep); - - Geometry geometry = (Geometry) latestDac.getSpatial(); - TubeTreeMesh mesh = (TubeTreeMesh) geometry.getMesh(); - BitSet bitSet = mesh.listCapVertices(endJointName); - assert bitSet.cardinality() == mesh.verticesPerCap(); - int result = bitSet.nextSetBit(0); - - assert result >= 0 : result; - return result; - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.1f * ropeRadius; - float far = 250f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(25f); - flyCam.setZoomSpeed(25f); - - cam.setLocation(new Vector3f(0f, 33f, 55f)); - cam.setRotation(new Quaternion(0f, 0.982f, -0.188f, 0f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - physicsSpace.getSolverInfo().setNumIterations(20); - } - - /** - * Count the steps in the indexed branch of the specified Armature. - * - * @param armature which Armature (not null) - * @param branchIndex the index of the branch (≥0) - * @return the count (≥0) - */ - private static int countSteps(Armature armature, int branchIndex) { - int numSteps = 0; - - while (true) { - String jointName = jointName(branchIndex, numSteps); - if (armature.getJoint(jointName) == null) { - break; - } else { - ++numSteps; - } - } - - return numSteps; - } - - /** - * Kinematically curl a branch of the specified Armature around the - * specified axis. - * - * @param armature the Armature to pose (not null) - * @param branchIndex (≥0) - * @param axis (in each joint's local coordinates, not null, not zero) - * @param totalAngle the angle between the first and last steps (in radians, - * may be negative) - */ - private static void curlBranch(Armature armature, int branchIndex, - Vector3f axis, float totalAngle) { - assert !MyVector3f.isZero(axis); - - // Count the steps in this branch. - int numSteps = countSteps(armature, branchIndex); - assert numSteps > 1 : numSteps; - - // Calculate local rotation. - float turnAngle = totalAngle / (numSteps - 1); - Quaternion turn = new Quaternion().fromAngleAxis(turnAngle, axis); - - // Apply the rotation to each Joint. - for (int stepIndex = 0; stepIndex < numSteps; ++stepIndex) { - String jointName = jointName(branchIndex, stepIndex); - Joint joint = armature.getJoint(jointName); - joint.setLocalRotation(turn); - } - } - - /** - * Delete the most recently added rope. - */ - private static void delete() { - DynamicAnimControl latestDac = dacs.peekLast(); - if (latestDac != null) { - latestDac.setEnabled(false); - latestDac.setPhysicsSpace(null); - Spatial spatial = latestDac.getSpatial(); - spatial.removeControl(latestDac); - meshesNode.detachChild(spatial); - sv.setSubject(null); - - dacs.removeLast(); - shapes.removeLast(); - - latestDac = dacs.peekLast(); - if (latestDac != null) { - spatial = latestDac.getSpatial(); - SkinningControl sControl - = spatial.getControl(SkinningControl.class); - sv.setSubject(sControl); - } - } - } - - /** - * Put the most recently added rope into dynamic mode, with gravity. - */ - private static void goLimp() { - DynamicAnimControl latestDac = dacs.peekLast(); - if (latestDac != null && latestDac.isReady()) { - PhysicsLink rootLink = latestDac.getTorsoLink(); - Vector3f gravity = latestDac.gravity(null); - latestDac.setDynamicSubtree(rootLink, gravity, false); - } - } - - /** - * Initialization of the latest DynamicAnimControl that takes place once all - * its links are ready for dynamic mode: add pinning joints and splices. - */ - private void initWhenReady() { - assert dacs.peekLast().isReady(); - /* - * Force a software skin update - * before invoking DacLinks.findManagerForVertex(). - */ - Spatial spatial = dacs.peekLast().getSpatial(); - SkinningControl sControl = spatial.getControl(SkinningControl.class); - sControl.render(renderManager, viewPort); - - RopeShape latestShape = shapes.peekLast(); - switch (latestShape) { - case Cross: - pinEnd(0); - pinEnd(1); - pinEnd(2); - pinEnd(3); - break; - - case Noose: - case NooseSpliced: - pinEnd(0); - spliceEnds(1, 2); - break; - - case Ring: - case RingSpliced: - spliceEnds(0, 1); - break; - - case Slackline: - pinEnd(0); - pinEnd(1); - break; - - default: - throw new IllegalArgumentException(shapes.toString()); - } - } - - /** - * Generate the name for the indexed Joint in the indexed branch. - * - * @param branchIndex the index of the branch containing the Joint (≥0, - * <numBranches) - * @param stepIndex the joint's index in the branch (≥0) - * @return the name (not null, not empty) - */ - private static String jointName(int branchIndex, int stepIndex) { - assert branchIndex >= 0 : branchIndex; - assert stepIndex >= 0 : stepIndex; - - String name = String.format("branch%d.bone%d", branchIndex, stepIndex); - return name; - } - - /** - * Initialization performed immediately after parsing the command-line - * arguments. - * - * @param showDialog when to show the JME settings dialog (not null) - * @param title for the title bar of the app's window - */ - private static void - mainStartup(final ShowDialog showDialog, final String title) { - RopeDemo application = new RopeDemo(); - - RectSizeLimits sizeLimits = new RectSizeLimits( - 530, 480, // min width, height - 2_048, 1_080 // max width, height - ); - proposedSettings = new DisplaySettings( - application, applicationName, sizeLimits) { - @Override - protected void applyOverrides(AppSettings settings) { - setShowDialog(showDialog); - settings.setAudioRenderer(null); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - if (settings.getSamples() < 1) { - settings.setSamples(4); // anti-aliasing - } - settings.setResizable(true); - settings.setTitle(title); // Customize the window's title bar. - } - }; - AppSettings appSettings = proposedSettings.initialize(); - if (appSettings == null) { - return; - } - - application.setSettings(appSettings); - /* - * If the settings dialog should be shown, - * it has already been shown by DisplaySettings.initialize(). - */ - application.setShowSettings(false); - application.start(); - } - - /** - * Generate an Armature composed of straight-line branches originating from - * the root joint. - * - * @param branchToNumSteps the number of joints in each branch (each ≥1) - * @param branchToStepOffsets the parent-to-child offset in each branch - * @return a new Armature in bind pose (not null) - */ - private static Armature makeArmature( - int[] branchToNumSteps, Vector3f[] branchToStepOffsets) { - assert branchToNumSteps.length > 0; - assert branchToNumSteps.length == branchToStepOffsets.length; - int numJoints = 1; - for (int numSteps : branchToNumSteps) { - assert numSteps >= 1 : numSteps; - numJoints += numSteps; - } - - Joint[] joints = new Joint[numJoints]; - Joint root = new Joint("root joint"); - joints[0] = root; - - int nextJointIndex = 1; - int numBranches = branchToNumSteps.length; - for (int branchIndex = 0; branchIndex < numBranches; ++branchIndex) { - Joint parent = root; - Vector3f stepOffset = branchToStepOffsets[branchIndex]; - int numSteps = branchToNumSteps[branchIndex]; - for (int stepIndex = 0; stepIndex < numSteps; ++stepIndex) { - String jointName = jointName(branchIndex, stepIndex); - Joint joint = new Joint(jointName); - joint.setLocalTranslation(stepOffset); - - joints[nextJointIndex] = joint; - parent.addChild(joint); - parent = joint; - ++nextJointIndex; - } - } - assert nextJointIndex == numJoints; - - Armature armature = new Armature(joints); - armature.saveBindPose(); - - return armature; - } - - /** - * Pin the endpoint of the indexed branch to its current location. - * - * @param branchIndex which branch to pin (≥0) - */ - private void pinEnd(int branchIndex) { - String[] capSpecs = capSpecs(branchIndex); - - DynamicAnimControl latestDac = dacs.peekLast(); - Vector3f sum = new Vector3f(); - PhysicsLink link - = latestDac.findManagerForVertex(capSpecs[0], sum, null); - - Vector3f pivot = new Vector3f(); // world coordinates - latestDac.findManagerForVertex(capSpecs[1], pivot, null); - sum.addLocal(pivot); - - latestDac.findManagerForVertex(capSpecs[2], pivot, null); - sum.addLocal(pivot); - - latestDac.pinToWorld(link, sum.divide(3f)); - } - - /** - * Disable one single-ended IK constraint of the most recently added rope. - */ - private static void pullAPin() { - DynamicAnimControl latestDac = dacs.peekLast(); - if (latestDac != null) { - IKJoint[] ikJoints = latestDac.listIKJoints(); - for (IKJoint ikJoint : ikJoints) { - Constraint constraint = ikJoint.getPhysicsJoint(); - if (constraint.isEnabled() && constraint.countEnds() == 1) { - constraint.setEnabled(false); - break; - } - } - } - } - - /** - * Triple-pin the ends of the indexed branches together. - * - * @param branchIndex1 (≥0) - * @param branchIndex2 (≥0) - */ - private void spliceEnds(int branchIndex1, int branchIndex2) { - PhysicsLink linkA; - PhysicsLink linkB; - Vector3f pivotA = new Vector3f(); // local coordinates in rigid body - Vector3f pivotB = new Vector3f(); - - String[] capSpecsA = capSpecs(branchIndex1); - String[] capSpecsB = capSpecs(branchIndex2); - - DynamicAnimControl latestDac = dacs.peekLast(); - linkA = latestDac.findManagerForVertex(capSpecsA[0], null, pivotA); - linkB = latestDac.findManagerForVertex(capSpecsB[0], null, pivotB); - latestDac.pinToSelf(linkA, linkB, pivotA, pivotB); - - linkA = latestDac.findManagerForVertex(capSpecsA[1], null, pivotA); - linkB = latestDac.findManagerForVertex(capSpecsB[2], null, pivotB); - latestDac.pinToSelf(linkA, linkB, pivotA, pivotB); - - linkA = latestDac.findManagerForVertex(capSpecsA[2], null, pivotA); - linkB = latestDac.findManagerForVertex(capSpecsB[1], null, pivotB); - latestDac.pinToSelf(linkA, linkB, pivotA, pivotB); - } - - /** - * Toggle mesh rendering of ropes on/off. - */ - private static void toggleMeshes() { - Spatial.CullHint hint = meshesNode.getLocalCullHint(); - if (hint == Spatial.CullHint.Inherit - || hint == Spatial.CullHint.Never) { - hint = Spatial.CullHint.Always; - } else if (hint == Spatial.CullHint.Always) { - hint = Spatial.CullHint.Never; - } - meshesNode.setCullHint(hint); - } - - /** - * Toggle the skeleton visualizer on/off. - */ - private static void toggleSkeleton() { - boolean enabled = sv.isEnabled(); - sv.setEnabled(!enabled); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DacConfiguration; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.IKJoint; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.bullet.joints.Constraint; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import java.util.ArrayDeque; +import java.util.BitSet; +import java.util.Deque; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MySkeleton; +import jme3utilities.MyString; +import jme3utilities.NameGenerator; +import jme3utilities.debug.SkeletonVisualizer; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.RectSizeLimits; +import jme3utilities.math.noise.Generator; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.mesh.TubeTreeMesh; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.DisplaySettings; +import jme3utilities.ui.DsEditOverlay; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.ShowDialog; + +/** + * Simulate ropes using DynamicAnimControl. + *

+ * Seen in the February 2019 demo video: + * https://www.youtube.com/watch?v=7PYDAyB5RCE + * + * @author Stephen Gold sgold@sonic.net + */ +public class RopeDemo extends PhysicsDemo { + // ************************************************************************* + // classes and enums + + /** + * Enumerate the rope shapes used in this demo. + */ + private enum RopeShape { + /** + * a rope with 4 ends + */ + Cross, + /** + * a noose, pinned at the standing end + */ + Noose, + /** + * a noose, pinned at the standing end, kinematically pre-spliced + */ + NooseSpliced, + /** + * a rope ring + */ + Ring, + /** + * a rope ring, kinematically pre-spliced + */ + RingSpliced, + /** + * a plain rope with slack, pinned at both ends + */ + Slackline + } + // ************************************************************************* + // constants and loggers + + /** + * cross-section radius for ropes (in mesh units) + */ + final private static float ropeRadius = 0.4f; + /** + * minimum curl radius for ropes (in mesh units) + */ + final private static float minCurlRadius = 4f * ropeRadius; + /** + * mass of each link (in physics mass units) + */ + final private static float linkMass = 1e-6f; + /** + * length of each rope segment (in mesh units) + */ + final private static float stepLength = 1.9f * minCurlRadius; + /** + * number of mesh loops in each tube segment + */ + final private static int loopsPerSegment = 3; + /** + * number of sample points per mesh loop + */ + final private static int samplesPerLoop = 12; + /** + * link configuration for leaf joints (shrunken hull shape) + */ + final private static LinkConfig leafConfig = new LinkConfig( + linkMass, MassHeuristic.Mass, ShapeHeuristic.VertexHull, + new Vector3f(0.84f, 0.84f, 0.84f), CenterHeuristic.Mean); + /** + * link configuration for non-leaf joints (stretched capsule shape) + */ + final private static LinkConfig ropeConfig = new LinkConfig( + linkMass, MassHeuristic.Mass, ShapeHeuristic.TwoSphere, + new Vector3f(1f, 1f, 2.5f), CenterHeuristic.Mean); + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(RopeDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = RopeDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * true once {@link #initWhenReady()} has been invoked for the latest rope + */ + private static boolean dacReadyInitDone = false; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * physics controls for the ropes, in order of creation + */ + final private static Deque dacs = new ArrayDeque<>(12); + /** + * shapes of the ropes, in order of creation + */ + final private static Deque shapes = new ArrayDeque<>(12); + /** + * proposed display settings (for editing) + */ + private static DisplaySettings proposedSettings; + /** + * AppState to manage the display-settings editor + */ + private static DsEditOverlay dseOverlay; + /** + * generate names for rope geometries + */ + final private static NameGenerator geometryNamer = new NameGenerator(); + /** + * parent for rope geometries + */ + final private static Node meshesNode = new Node("meshes node"); + /** + * visualizer for the Armature of the most recently added rope + */ + private static SkeletonVisualizer sv; + // ************************************************************************* + // constructors + + /** + * Instantiate the RopeDemo application. + */ + public RopeDemo() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the RopeDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Process any command-line arguments. + ShowDialog showDialog = ShowDialog.Never; + for (String arg : arguments) { + switch (arg) { + case "--deleteOnly": + Heart.deleteStoredSettings(applicationName); + return; + + case "--showSettingsDialog": + showDialog = ShowDialog.FirstTime; + break; + + case "--verbose": + Heart.setLoggingLevels(Level.INFO); + break; + + default: + logger.log(Level.WARNING, + "Ignored unknown command-line argument {0}", + MyString.quote(arg)); + } + } + mainStartup(showDialog, title); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + dseOverlay = new DsEditOverlay(proposedSettings); + dseOverlay.setBackgroundColor(new ColorRGBA(0.05f, 0f, 0f, 1f)); + boolean success = stateManager.attach(dseOverlay); + assert success; + super.acorusInit(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + addLighting(); + + float halfExtent = 650f; + float topY = 0f; + attachCubePlatform(halfExtent, topY); + addSkeleton(); + + rootNode.attachChild(meshesNode); + } + + /** + * Configure the PhysicsDumper. + */ + @Override + public void configureDumper() { + super.configureDumper(); + + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.JointsInSpaces, true); + } + + /** + * Calculate screen bounds for a detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes on the right side of the viewport. + float margin = 10f; // in pixels + float height = viewPortHeight - (2f * margin); + float width = 260f; // in pixels + float leftX = viewPortWidth - (width + margin); + float topY = margin + height; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Initialize materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + ColorRGBA taupe = new ColorRGBA().setAsSrgb(0.6f, 0.5f, 0.4f, 1f); + Material ropeMaterial + = MyAsset.createShadedMaterial(assetManager, taupe); + registerMaterial("rope", ropeMaterial); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of debug axis arrows when visible. + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 0.5f; + } + + /** + * Add application-specific hotkey bindings and override existing ones. + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind("add Cross", KeyInput.KEY_F2); + dim.bind("add Ring", KeyInput.KEY_F3); + dim.bind("add RingSpliced", KeyInput.KEY_F4); + dim.bind("add Noose", KeyInput.KEY_F7); + dim.bind("add NooseSpliced", KeyInput.KEY_F8); + dim.bind("add Slackline", KeyInput.KEY_F1); + + dim.bind("delete", KeyInput.KEY_BACK, KeyInput.KEY_DELETE); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); + dim.bind("go limp", KeyInput.KEY_SPACE); + dim.bind("pull a pin", KeyInput.KEY_X); + dim.bind("save", KeyInput.KEY_SEMICOLON); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleDebug, KeyInput.KEY_SLASH); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind("toggle meshes", KeyInput.KEY_M); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind("toggle skeleton", KeyInput.KEY_V); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "delete": + delete(); + return; + + case asEditDisplaySettings: + activateInputMode("dsEdit"); + return; + + case "go limp": + goLimp(); + return; + + case "pull a pin": + pullAPin(); + return; + + case "toggle meshes": + toggleMeshes(); + return; + case "toggle skeleton": + toggleSkeleton(); + return; + + default: + } + + String[] words = actionString.split(" "); + if (words.length >= 2 && "add".equals(words[0])) { + addRope(words[1]); + return; + } + + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + dseOverlay.onViewPortResize(newWidth, newHeight); + proposedSettings.resize(newWidth, newHeight); + + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + DynamicAnimControl latestDac = dacs.peekLast(); + if (!dacReadyInitDone && latestDac != null && latestDac.isReady()) { + initWhenReady(); + dacReadyInitDone = true; + } + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the main scene. + */ + private void addLighting() { + ColorRGBA ambientColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootNode.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -2f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + sun.setName("sun"); + + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.5f); + viewPort.addProcessor(dlsr); + } + + /** + * Add a kinematic rope (with the named shape) to the scene. + * + * @param shapeName the name of the shape to add (not null, not empty) + */ + private void addRope(String shapeName) { + DynamicAnimControl latestDac = dacs.peekLast(); + if (latestDac != null && latestDac.getTorsoLink().isKinematic()) { + // only one kinematic rope at a time + delete(); + } + + RopeShape shape = RopeShape.valueOf(shapeName); + switch (shape) { + case Cross: + addRopeCross(); + break; + case Noose: + addRopeNoose(false); + break; + case NooseSpliced: + addRopeNoose(true); + break; + case Ring: + addRopeRing(false); + break; + case RingSpliced: + addRopeRing(true); + break; + case Slackline: + addRopeSlackline(); + break; + default: + throw new IllegalArgumentException(shapeName); + } + + shapes.addLast(shape); + } + + /** + * Add a rope cross to the scene. + */ + private void addRopeCross() { + // Generate a cross-shaped Armature. + int[] stepCounts = {5, 5, 5, 5}; + Vector3f[] stepOffsets = { + new Vector3f(stepLength, 0f, 0f), + new Vector3f(-stepLength, 0f, 0f), + new Vector3f(0f, 0f, stepLength), + new Vector3f(0f, 0f, -stepLength) + }; + Armature armature = makeArmature(stepCounts, stepOffsets); + + TubeTreeMesh ropeMesh = new TubeTreeMesh( + armature, ropeRadius, 0f, loopsPerSegment, samplesPerLoop); + + String geometryName = geometryNamer.unique("rope cross"); + Geometry geometry = new Geometry(geometryName, ropeMesh); + + addRopeSpatial(armature, geometry); + + // Curl all branches clockwise to introduce some slack. + curlBranch(armature, 0, Vector3f.UNIT_Y, 0.2f); + curlBranch(armature, 1, Vector3f.UNIT_Y, 0.2f); + curlBranch(armature, 2, Vector3f.UNIT_Y, 0.2f); + curlBranch(armature, 3, Vector3f.UNIT_Y, 0.2f); + } + + /** + * Add a noose to the scene. + * + * @param kinematicSplice true to pre-splice the ends kinematically, false + * to leave it Y-shaped until dynamic mode is set + */ + private void addRopeNoose(boolean kinematicSplice) { + // Generate a Y-shaped Armature. Branch0 forms the stem of the Y. + int[] stepCounts = {6, 6, 6}; + float dx = stepLength * MyMath.rootHalf; + Vector3f[] stepOffsets = { + new Vector3f(stepLength, 0f, 0f), + new Vector3f(-dx, 0f, -dx), + new Vector3f(-dx, 0f, dx) + }; + Armature armature = makeArmature(stepCounts, stepOffsets); + + TubeTreeMesh ropeMesh = new TubeTreeMesh( + armature, ropeRadius, 0f, loopsPerSegment, samplesPerLoop); + String geometryName = geometryNamer.unique("noose"); + Geometry geometry = new Geometry(geometryName, ropeMesh); + + addRopeSpatial(armature, geometry); + + if (kinematicSplice) { + // Curl branch1 and branch2 toward one another, 90 degrees each. + curlBranch(armature, 1, Vector3f.UNIT_Y, FastMath.HALF_PI); + curlBranch(armature, 2, Vector3f.UNIT_Y, -FastMath.HALF_PI); + } + } + + /** + * Add a rope ring to the scene. + * + * @param kinematicSplice true to pre-splice the ends kinematically, false + * to leave it linear until dynamic mode is set + */ + private void addRopeRing(boolean kinematicSplice) { + // Generate a double-ended straight-line Armature. + int[] stepCounts = {8, 8}; + Vector3f[] stepOffsets = { + new Vector3f(stepLength, 0f, 0f), + new Vector3f(-stepLength, 0f, 0f) + }; + Armature armature = makeArmature(stepCounts, stepOffsets); + + TubeTreeMesh ropeMesh = new TubeTreeMesh( + armature, ropeRadius, 0f, loopsPerSegment, samplesPerLoop); + + String geometryName = geometryNamer.unique("rope ring"); + Geometry geometry = new Geometry(geometryName, ropeMesh); + + addRopeSpatial(armature, geometry); + + if (kinematicSplice) { + // Curl branch0 and branch1 toward one another, 180 degrees each. + curlBranch(armature, 0, Vector3f.UNIT_Y, FastMath.PI); + curlBranch(armature, 1, Vector3f.UNIT_Y, -FastMath.PI); + } + } + + /** + * Add a plain rope to the scene. The rope begins in the X-Z plane. + */ + private void addRopeSlackline() { + // Generate a double-ended straight-line Armature. + int[] stepCounts = {8, 8}; + Vector3f[] stepOffsets = { + new Vector3f(0f, 0f, stepLength), + new Vector3f(0f, 0f, -stepLength) + }; + Armature armature = makeArmature(stepCounts, stepOffsets); + + float leafOvershoot = 0.8f * ropeRadius; + TubeTreeMesh ropeMesh = new TubeTreeMesh(armature, ropeRadius, + leafOvershoot, loopsPerSegment, samplesPerLoop); + String geometryName = geometryNamer.unique("slackline"); + Geometry geometry = new Geometry(geometryName, ropeMesh); + + addRopeSpatial(armature, geometry); + + // Curl both branches clockwise to introduce some slack. + curlBranch(armature, 0, Vector3f.UNIT_Y, 0.2f); + curlBranch(armature, 1, Vector3f.UNIT_Y, 0.2f); + } + + /** + * Code for adding a rope that's shared by all shapes. + * + * @param armature (not null) + * @param spatial (not null) + */ + private void addRopeSpatial(Armature armature, Spatial spatial) { + // Set a random elevation and Y-axis rotation. + Generator random = getGenerator(); + float elevation = random.nextFloat(12f, 24f); + float rotationAngle = random.nextFloat(0f, FastMath.TWO_PI); + spatial.move(0f, elevation, 0f); + spatial.rotate(0f, rotationAngle, 0f); + + spatial.setCullHint(Spatial.CullHint.Never); + Material ropeMaterial = findMaterial("rope"); + spatial.setMaterial(ropeMaterial); + spatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + SkinningControl sControl = new SkinningControl(armature); + spatial.addControl(sControl); + sControl.setHardwareSkinningPreferred(false); + sv.setSubject(sControl); + + assert stepLength < 2f * minCurlRadius : stepLength; + float maxAngle = FastMath.acos(stepLength / (2f * minCurlRadius)); + RangeOfMotion rom = new RangeOfMotion(maxAngle); + + DynamicAnimControl dac = new DynamicAnimControl(); + for (Joint joint : MySkeleton.preOrderJoints(armature)) { + if (joint.getParent() != null) { + String jointName = joint.getName(); + boolean isLeafJoint = joint.getChildren().isEmpty(); + LinkConfig linkConfig = isLeafJoint ? leafConfig : ropeConfig; + dac.link(jointName, linkConfig, rom); + } + } + dac.setConfig(DacConfiguration.torsoName, ropeConfig); + dac.setDamping(0.9f); + dac.setGravity(new Vector3f(0f, -120f, 0f)); + + spatial.addControl(dac); + PhysicsSpace physicsSpace = getPhysicsSpace(); + dac.setPhysicsSpace(physicsSpace); + meshesNode.attachChild(spatial); + + setFrictionAll(9e9f); + + dacs.addLast(dac); + dacReadyInitDone = false; + } + + /** + * Add the skeleton visualizer the scene. + */ + private void addSkeleton() { + sv = new SkeletonVisualizer(assetManager, null); + rootNode.addControl(sv); + sv.setLineColor(ColorRGBA.Red); + sv.setHeadColor(0, ColorRGBA.Green); + sv.setHeadSize(8); + } + + /** + * Choose 3 distinct vertices in the end cap of the indexed branch. + * + * @param branchIndex which branch (≥0) + * @return a new array of 3 vertex specifiers + */ + private static String[] capSpecs(int branchIndex) { + int capVertex0 = capVertex0(branchIndex); + String geometryName = dacs.peekLast().getSpatial().getName(); + String[] result = new String[3]; + result[0] = String.format("%d/%s", capVertex0, geometryName); + + if (samplesPerLoop == 6) { + result[1] = String.format("%d/%s", capVertex0 + 1, geometryName); + result[2] = String.format("%d/%s", capVertex0 + 7, geometryName); + } else if (samplesPerLoop == 9) { + result[1] = String.format("%d/%s", capVertex0 + 4, geometryName); + result[2] = String.format("%d/%s", capVertex0 + 13, geometryName); + } else if (samplesPerLoop == 12) { + result[1] = String.format("%d/%s", capVertex0 + 7, geometryName); + result[2] = String.format("%d/%s", capVertex0 + 19, geometryName); + } else { + String message = "samplesPerLoop = " + samplesPerLoop; + throw new IllegalStateException(message); + } + + return result; + } + + /** + * Find the index of the first vertex in the end cap of the indexed branch. + * + * @param branchIndex which branch(≥0) + * @return the vertex index (≥0) + */ + private static int capVertex0(int branchIndex) { + assert branchIndex >= 0 : branchIndex; + + DynamicAnimControl latestDac = dacs.peekLast(); + Armature armature = latestDac.getArmature(); + int lastStep = countSteps(armature, branchIndex) - 1; + String endJointName = jointName(branchIndex, lastStep); + + Geometry geometry = (Geometry) latestDac.getSpatial(); + TubeTreeMesh mesh = (TubeTreeMesh) geometry.getMesh(); + BitSet bitSet = mesh.listCapVertices(endJointName); + assert bitSet.cardinality() == mesh.verticesPerCap(); + int result = bitSet.nextSetBit(0); + + assert result >= 0 : result; + return result; + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.1f * ropeRadius; + float far = 250f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(25f); + flyCam.setZoomSpeed(25f); + + cam.setLocation(new Vector3f(0f, 33f, 55f)); + cam.setRotation(new Quaternion(0f, 0.982f, -0.188f, 0f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + physicsSpace.getSolverInfo().setNumIterations(20); + } + + /** + * Count the steps in the indexed branch of the specified Armature. + * + * @param armature which Armature (not null) + * @param branchIndex the index of the branch (≥0) + * @return the count (≥0) + */ + private static int countSteps(Armature armature, int branchIndex) { + int numSteps = 0; + + while (true) { + String jointName = jointName(branchIndex, numSteps); + if (armature.getJoint(jointName) == null) { + break; + } else { + ++numSteps; + } + } + + return numSteps; + } + + /** + * Kinematically curl a branch of the specified Armature around the + * specified axis. + * + * @param armature the Armature to pose (not null) + * @param branchIndex (≥0) + * @param axis (in each joint's local coordinates, not null, not zero) + * @param totalAngle the angle between the first and last steps (in radians, + * may be negative) + */ + private static void curlBranch(Armature armature, int branchIndex, + Vector3f axis, float totalAngle) { + assert !MyVector3f.isZero(axis); + + // Count the steps in this branch. + int numSteps = countSteps(armature, branchIndex); + assert numSteps > 1 : numSteps; + + // Calculate local rotation. + float turnAngle = totalAngle / (numSteps - 1); + Quaternion turn = new Quaternion().fromAngleAxis(turnAngle, axis); + + // Apply the rotation to each Joint. + for (int stepIndex = 0; stepIndex < numSteps; ++stepIndex) { + String jointName = jointName(branchIndex, stepIndex); + Joint joint = armature.getJoint(jointName); + joint.setLocalRotation(turn); + } + } + + /** + * Delete the most recently added rope. + */ + private static void delete() { + DynamicAnimControl latestDac = dacs.peekLast(); + if (latestDac != null) { + latestDac.setEnabled(false); + latestDac.setPhysicsSpace(null); + Spatial spatial = latestDac.getSpatial(); + spatial.removeControl(latestDac); + meshesNode.detachChild(spatial); + sv.setSubject(null); + + dacs.removeLast(); + shapes.removeLast(); + + latestDac = dacs.peekLast(); + if (latestDac != null) { + spatial = latestDac.getSpatial(); + SkinningControl sControl + = spatial.getControl(SkinningControl.class); + sv.setSubject(sControl); + } + } + } + + /** + * Put the most recently added rope into dynamic mode, with gravity. + */ + private static void goLimp() { + DynamicAnimControl latestDac = dacs.peekLast(); + if (latestDac != null && latestDac.isReady()) { + PhysicsLink rootLink = latestDac.getTorsoLink(); + Vector3f gravity = latestDac.gravity(null); + latestDac.setDynamicSubtree(rootLink, gravity, false); + } + } + + /** + * Initialization of the latest DynamicAnimControl that takes place once all + * its links are ready for dynamic mode: add pinning joints and splices. + */ + private void initWhenReady() { + assert dacs.peekLast().isReady(); + /* + * Force a software skin update + * before invoking DacLinks.findManagerForVertex(). + */ + Spatial spatial = dacs.peekLast().getSpatial(); + SkinningControl sControl = spatial.getControl(SkinningControl.class); + sControl.render(renderManager, viewPort); + + RopeShape latestShape = shapes.peekLast(); + switch (latestShape) { + case Cross: + pinEnd(0); + pinEnd(1); + pinEnd(2); + pinEnd(3); + break; + + case Noose: + case NooseSpliced: + pinEnd(0); + spliceEnds(1, 2); + break; + + case Ring: + case RingSpliced: + spliceEnds(0, 1); + break; + + case Slackline: + pinEnd(0); + pinEnd(1); + break; + + default: + throw new IllegalArgumentException(shapes.toString()); + } + } + + /** + * Generate the name for the indexed Joint in the indexed branch. + * + * @param branchIndex the index of the branch containing the Joint (≥0, + * <numBranches) + * @param stepIndex the joint's index in the branch (≥0) + * @return the name (not null, not empty) + */ + private static String jointName(int branchIndex, int stepIndex) { + assert branchIndex >= 0 : branchIndex; + assert stepIndex >= 0 : stepIndex; + + String name = String.format("branch%d.bone%d", branchIndex, stepIndex); + return name; + } + + /** + * Initialization performed immediately after parsing the command-line + * arguments. + * + * @param showDialog when to show the JME settings dialog (not null) + * @param title for the title bar of the app's window + */ + private static void + mainStartup(final ShowDialog showDialog, final String title) { + RopeDemo application = new RopeDemo(); + + RectSizeLimits sizeLimits = new RectSizeLimits( + 530, 480, // min width, height + 2_048, 1_080 // max width, height + ); + proposedSettings = new DisplaySettings( + application, applicationName, sizeLimits) { + @Override + protected void applyOverrides(AppSettings settings) { + setShowDialog(showDialog); + settings.setAudioRenderer(null); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + if (settings.getSamples() < 1) { + settings.setSamples(4); // anti-aliasing + } + settings.setResizable(true); + settings.setTitle(title); // Customize the window's title bar. + } + }; + AppSettings appSettings = proposedSettings.initialize(); + if (appSettings == null) { + return; + } + + application.setSettings(appSettings); + /* + * If the settings dialog should be shown, + * it has already been shown by DisplaySettings.initialize(). + */ + application.setShowSettings(false); + application.start(); + } + + /** + * Generate an Armature composed of straight-line branches originating from + * the root joint. + * + * @param branchToNumSteps the number of joints in each branch (each ≥1) + * @param branchToStepOffsets the parent-to-child offset in each branch + * @return a new Armature in bind pose (not null) + */ + private static Armature makeArmature( + int[] branchToNumSteps, Vector3f[] branchToStepOffsets) { + assert branchToNumSteps.length > 0; + assert branchToNumSteps.length == branchToStepOffsets.length; + int numJoints = 1; + for (int numSteps : branchToNumSteps) { + assert numSteps >= 1 : numSteps; + numJoints += numSteps; + } + + Joint[] joints = new Joint[numJoints]; + Joint root = new Joint("root joint"); + joints[0] = root; + + int nextJointIndex = 1; + int numBranches = branchToNumSteps.length; + for (int branchIndex = 0; branchIndex < numBranches; ++branchIndex) { + Joint parent = root; + Vector3f stepOffset = branchToStepOffsets[branchIndex]; + int numSteps = branchToNumSteps[branchIndex]; + for (int stepIndex = 0; stepIndex < numSteps; ++stepIndex) { + String jointName = jointName(branchIndex, stepIndex); + Joint joint = new Joint(jointName); + joint.setLocalTranslation(stepOffset); + + joints[nextJointIndex] = joint; + parent.addChild(joint); + parent = joint; + ++nextJointIndex; + } + } + assert nextJointIndex == numJoints; + + Armature armature = new Armature(joints); + armature.saveBindPose(); + + return armature; + } + + /** + * Pin the endpoint of the indexed branch to its current location. + * + * @param branchIndex which branch to pin (≥0) + */ + private void pinEnd(int branchIndex) { + String[] capSpecs = capSpecs(branchIndex); + + DynamicAnimControl latestDac = dacs.peekLast(); + Vector3f sum = new Vector3f(); + PhysicsLink link + = latestDac.findManagerForVertex(capSpecs[0], sum, null); + + Vector3f pivot = new Vector3f(); // world coordinates + latestDac.findManagerForVertex(capSpecs[1], pivot, null); + sum.addLocal(pivot); + + latestDac.findManagerForVertex(capSpecs[2], pivot, null); + sum.addLocal(pivot); + + latestDac.pinToWorld(link, sum.divide(3f)); + } + + /** + * Disable one single-ended IK constraint of the most recently added rope. + */ + private static void pullAPin() { + DynamicAnimControl latestDac = dacs.peekLast(); + if (latestDac != null) { + IKJoint[] ikJoints = latestDac.listIKJoints(); + for (IKJoint ikJoint : ikJoints) { + Constraint constraint = ikJoint.getPhysicsJoint(); + if (constraint.isEnabled() && constraint.countEnds() == 1) { + constraint.setEnabled(false); + break; + } + } + } + } + + /** + * Triple-pin the ends of the indexed branches together. + * + * @param branchIndex1 (≥0) + * @param branchIndex2 (≥0) + */ + private void spliceEnds(int branchIndex1, int branchIndex2) { + PhysicsLink linkA; + PhysicsLink linkB; + Vector3f pivotA = new Vector3f(); // local coordinates in rigid body + Vector3f pivotB = new Vector3f(); + + String[] capSpecsA = capSpecs(branchIndex1); + String[] capSpecsB = capSpecs(branchIndex2); + + DynamicAnimControl latestDac = dacs.peekLast(); + linkA = latestDac.findManagerForVertex(capSpecsA[0], null, pivotA); + linkB = latestDac.findManagerForVertex(capSpecsB[0], null, pivotB); + latestDac.pinToSelf(linkA, linkB, pivotA, pivotB); + + linkA = latestDac.findManagerForVertex(capSpecsA[1], null, pivotA); + linkB = latestDac.findManagerForVertex(capSpecsB[2], null, pivotB); + latestDac.pinToSelf(linkA, linkB, pivotA, pivotB); + + linkA = latestDac.findManagerForVertex(capSpecsA[2], null, pivotA); + linkB = latestDac.findManagerForVertex(capSpecsB[1], null, pivotB); + latestDac.pinToSelf(linkA, linkB, pivotA, pivotB); + } + + /** + * Toggle mesh rendering of ropes on/off. + */ + private static void toggleMeshes() { + Spatial.CullHint hint = meshesNode.getLocalCullHint(); + if (hint == Spatial.CullHint.Inherit + || hint == Spatial.CullHint.Never) { + hint = Spatial.CullHint.Always; + } else if (hint == Spatial.CullHint.Always) { + hint = Spatial.CullHint.Never; + } + meshesNode.setCullHint(hint); + } + + /** + * Toggle the skeleton visualizer on/off. + */ + private static void toggleSkeleton() { + boolean enabled = sv.isEnabled(); + sv.setEnabled(!enabled); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/SeJointDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/SeJointDemo.java index 5bc5f35ff..38803c4ee 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/SeJointDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/SeJointDemo.java @@ -1,673 +1,673 @@ -/* - Copyright (c) 2018-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.joints.ConeJoint; -import com.jme3.bullet.joints.HingeJoint; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.joints.Point2PointJoint; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.joints.SixDofSpringJoint; -import com.jme3.bullet.joints.SliderJoint; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.math.noise.Generator; -import jme3utilities.mesh.Icosphere; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; - -/** - * Test/demonstrate single-ended joints. - *

- * Seen in the November 2018 demo video: - * https://www.youtube.com/watch?v=Mh9k5AfWzbg - * - * @author Stephen Gold sgold@sonic.net - */ -public class SeJointDemo extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * seed radius (in mesh units) - */ - final private static float seedRadius = 4.8f; - /** - * upper limit on the number of seed groups - */ - final private static int maxGroups = 4; - /** - * upper limit on the number of seeds - */ - final private static int maxSeeds = 300; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(SeJointDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = SeJointDemo.class.getSimpleName(); - /** - * joint axes in physics-space coordinates (unit vector) - */ - final private static Vector3f axisInWorld = Vector3f.UNIT_Z; - // ************************************************************************* - // fields - - /** - * status displayed in the upper-left corner of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - private static CollisionShape seedShape; - /** - * material for each type of seed - */ - final private static Material[] materials = new Material[maxGroups]; - - final private static Matrix3f rotInSeed = new Matrix3f(); - final private static Matrix3f rotInWorld = new Matrix3f(); - /** - * mesh for visualizing seeds - */ - private static Mesh seedMesh; - /** - * scene-graph node for visualizing seeds - */ - final private static Node meshesNode = new Node("meshes node"); - /** - * name of the test that's currently running - */ - private static String testName = "p2p"; - /** - * joint axis in each seed's local coordinates (unit vector) - */ - final private static Vector3f axisInSeed = new Vector3f(); - /** - * gravity vector (in physics-space coordinates) - */ - final private static Vector3f gravity = new Vector3f(); - /** - * offset of pivot relative to each seed (in scaled shape coordinates) - */ - final private static Vector3f pivotInSeed = new Vector3f(); - /** - * pivot location for each group (in physics-space coordinates) - */ - final private static Vector3f[] pivotLocations = new Vector3f[maxGroups]; - /** - * scale factors that determine the seed's shape - */ - final private static Vector3f seedScale = new Vector3f(); - // ************************************************************************* - // constructors - - /** - * Instantiate the SeJointDemo application. - */ - public SeJointDemo() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the SeJointDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new SeJointDemo(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - guiNode.attachChild(statusText); - - super.acorusInit(); - - configureCamera(); - configureDumper(); - configurePhysics(); - configureGroups(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - addLighting(); - - float length = 32f; - attachWorldAxes(length); - - int numRefineSteps = 1; - seedMesh = new Icosphere(numRefineSteps, seedRadius); - seedShape = new MultiSphere(seedRadius); - - meshesNode.setCullHint(Spatial.CullHint.Never); // with meshes visible - rootNode.attachChild(meshesNode); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - super.configureDumper(); - - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.JointsInSpaces, true); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 20f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind("add", KeyInput.KEY_INSERT); - - dim.bind(asDumpScene, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - dim.bind("signal shower", KeyInput.KEY_I); - - dim.bind("test 6dof", KeyInput.KEY_F6); - dim.bind("test 6dofSpring", KeyInput.KEY_F7); - dim.bind("test cone", KeyInput.KEY_F3); - dim.bind("test hinge", KeyInput.KEY_F2); - dim.bind("test new6dof", KeyInput.KEY_F8); - dim.bind("test p2p", KeyInput.KEY_F1); - dim.bind("test slider", KeyInput.KEY_F4); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind("toggle view", KeyInput.KEY_SLASH); - } - - /** - * Process an action that wasn't handled by the active InputMode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "add": - addSeed(); - return; - - case "test 6dof": - cleanupAfterTest(); - testName = "6dof"; - return; - case "test 6dofSpring": - cleanupAfterTest(); - testName = "6dofSpring"; - return; - case "test cone": - cleanupAfterTest(); - testName = "cone"; - return; - case "test hinge": - cleanupAfterTest(); - testName = "hinge"; - return; - case "test new6dof": - cleanupAfterTest(); - testName = "new6dof"; - return; - case "test p2p": - cleanupAfterTest(); - testName = "p2p"; - return; - case "test slider": - cleanupAfterTest(); - testName = "slider"; - return; - - case "toggle view": - toggleMeshes(); - togglePhysicsDebug(); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - statusText.setLocalTranslation(0f, newHeight, 0f); - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - Signals signals = getSignals(); - if (signals.test("shower")) { - addSeed(); - } - - updateStatusText(); - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the main scene. - */ - private void addLighting() { - ColorRGBA ambientColor = new ColorRGBA(2f, 2f, 2f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootNode.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - sun.setName("sun"); - } - - /** - * Add a dynamic rigid body to the scene and PhysicsSpace. - */ - private void addSeed() { - int numSeeds = meshesNode.getChildren().size(); - if (numSeeds >= maxSeeds) { - return; // too many seeds - } - /* - * All seeds share a single spherical mesh - * and a single spherical collision shape. - * Non-uniform scaling gives seeds their shape. - */ - int numGroups = maxGroups; // for most tests - switch (testName) { - case "6dof": - case "6dofSpring": - case "new6dof": - seedScale.set(3f, 1f, 1f); - break; - - case "cone": - numGroups = 1; - seedScale.set(3f, 1f, 1f); - break; - - case "hinge": - seedScale.set(1f, 2f, 1.5f); - break; - - case "p2p": - seedScale.set(1f, 1f, 1f); - break; - - case "slider": - numGroups = 3; - seedScale.set(1f, 2f, 2f); - break; - - default: - throw new IllegalStateException("testName = " + testName); - } - seedShape.setScale(seedScale); - - // Randomize which group the new seed is in. - Generator random = getGenerator(); - int groupIndex = random.nextInt(0, numGroups - 1); - Material material = materials[groupIndex]; - Vector3f pivotInWorld = pivotLocations[groupIndex]; - - // Randomize the new seed's initial location and velocity. - Vector3f velocity = random.nextVector3f(); - Vector3f location = random.nextVector3f(); - location.multLocal(20f, 40f, 20f); - location.addLocal(0f, 160f, 0f); - - Geometry geometry = new Geometry("seed", seedMesh); - meshesNode.attachChild(geometry); - geometry.setMaterial(material); - geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - geometry.move(location); - - RigidBodyControl rbc = new RigidBodyControl(seedShape); - rbc.setApplyScale(true); - rbc.setKinematic(false); - rbc.setLinearDamping(0.5f); - rbc.setLinearVelocity(velocity); - rbc.setPhysicsLocation(location); - rbc.setSleepingThresholds(0f, 0f); // never sleep - - PhysicsJoint joint; - switch (testName) { - case "6dof": - gravity.zero(); - pivotInSeed.set(40f, 0f, 0f); - rotInSeed.loadIdentity(); - float angle = (groupIndex - 1) * FastMath.HALF_PI; - rotInWorld.fromAngleAxis(angle, Vector3f.UNIT_Z); - JointEnd referenceFrame = JointEnd.B; - SixDofJoint sixDofJoint = new SixDofJoint(rbc, pivotInSeed, - pivotInWorld, rotInSeed, rotInWorld, referenceFrame); - sixDofJoint.setAngularLowerLimit(new Vector3f(0f, -1f, -1f)); - sixDofJoint.setAngularUpperLimit(new Vector3f(0f, 1f, 1f)); - joint = sixDofJoint; - break; - - case "6dofSpring": - gravity.zero(); - pivotInSeed.set(40f, 0f, 0f); - rotInSeed.loadIdentity(); - angle = (groupIndex - 1) * FastMath.HALF_PI; - rotInWorld.fromAngleAxis(angle, Vector3f.UNIT_Z); - referenceFrame = JointEnd.B; - SixDofSpringJoint springJoint = new SixDofSpringJoint( - rbc, pivotInSeed, pivotInWorld, rotInSeed, rotInWorld, - referenceFrame); - springJoint.setAngularLowerLimit(new Vector3f(0f, -1f, -1f)); - springJoint.setAngularUpperLimit(new Vector3f(0f, 1f, 1f)); - joint = springJoint; - break; - - case "cone": - gravity.zero(); - pivotInSeed.set(80f, 0f, 0f); - rotInSeed.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z); - ConeJoint coneJoint - = new ConeJoint(rbc, pivotInSeed, rotInSeed); - coneJoint.setLimit(1f, 1f, 1e30f); // radians - joint = coneJoint; - break; - - case "hinge": - axisInSeed.set(40f, 40f, 0f).normalizeLocal(); - gravity.set(0f, -40f, 0f); - pivotInSeed.set(0f, 40f, 0f); - referenceFrame = JointEnd.A; - joint = new HingeJoint(rbc, pivotInSeed, pivotInWorld, - axisInSeed, axisInWorld, referenceFrame); - break; - - case "new6dof": - gravity.zero(); - pivotInSeed.set(40f, 0f, 0f); - rotInSeed.loadIdentity(); - angle = (groupIndex - 1) * FastMath.HALF_PI; - rotInWorld.fromAngleAxis(angle, Vector3f.UNIT_Z); - New6Dof nJoint = new New6Dof(rbc, pivotInSeed, pivotInWorld, - rotInSeed, rotInWorld, RotationOrder.XYZ); - RotationMotor x = nJoint.getRotationMotor(PhysicsSpace.AXIS_X); - x.set(MotorParam.LowerLimit, 0f); - x.set(MotorParam.UpperLimit, 0f); - RotationMotor y = nJoint.getRotationMotor(PhysicsSpace.AXIS_Y); - y.set(MotorParam.LowerLimit, -1f); - y.set(MotorParam.UpperLimit, 1f); - RotationMotor z = nJoint.getRotationMotor(PhysicsSpace.AXIS_Z); - z.set(MotorParam.LowerLimit, -1f); - z.set(MotorParam.UpperLimit, 1f); - joint = nJoint; - break; - - case "p2p": - gravity.set(0f, -40f, 0f); - pivotInSeed.set(20f, 0f, 0f); - joint = new Point2PointJoint(rbc, pivotInSeed, pivotInWorld); - break; - - case "slider": - gravity.set(0f, 0f, 0f); // TODO - pivotInSeed.set(40f, 0f, 0f); - referenceFrame = JointEnd.B; - joint = new SliderJoint( - rbc, pivotInSeed, pivotInWorld, referenceFrame); - break; - - default: - throw new IllegalStateException("testName = " + testName); - } - - PhysicsSpace physicsSpace = getPhysicsSpace(); - rbc.setPhysicsSpace(physicsSpace); - rbc.setGravity(gravity); // must be set *after* setPhysicsSpace! - - physicsSpace.addJoint(joint); - - geometry.addControl(rbc); - } - - /** - * Clean up after a test. - */ - private void cleanupAfterTest() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - Collection jointList = physicsSpace.getJointList(); - for (PhysicsJoint joint : jointList) { - joint.destroy(); - physicsSpace.removeJoint(joint); - } - - List seeds = meshesNode.getChildren(); - for (Spatial geometry : seeds) { - RigidBodyControl rbc = geometry.getControl(RigidBodyControl.class); - rbc.setPhysicsSpace(null); - geometry.removeControl(rbc); - geometry.removeFromParent(); - } - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.8f; - float far = 4000f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(160f); - flyCam.setZoomSpeed(160f); - - cam.setLocation(new Vector3f(106f, 96.8f, 374.8f)); - cam.setRotation(new Quaternion(0f, 0.9759f, -0.04f, -0.2136f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure seed groups during startup. Seeds are divided into 4 groups, - * each with its own material and pivot location. - */ - private void configureGroups() { - ColorRGBA[] seedColors = new ColorRGBA[maxGroups]; - seedColors[0] = new ColorRGBA(0.2f, 0f, 0f, 1f); - seedColors[1] = new ColorRGBA(0f, 0.07f, 0f, 1f); - seedColors[2] = new ColorRGBA(0f, 0f, 0.3f, 1f); - seedColors[3] = new ColorRGBA(0.2f, 0.1f, 0f, 1f); - for (int i = 0; i < maxGroups; ++i) { - ColorRGBA color = seedColors[i]; - materials[i] = MyAsset.createShinyMaterial(assetManager, color); - materials[i].setFloat("Shininess", 15f); - } - - // The 4 pivot locations are arranged in a square in the X-Y plane. - pivotLocations[0] = new Vector3f(0f, 60f, 0f); - pivotLocations[1] = new Vector3f(-60f, 0f, 0f); - pivotLocations[2] = new Vector3f(0f, -60f, 0f); - pivotLocations[3] = new Vector3f(60f, 0f, 0f); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.setAccuracy(0.1f); // 100-msec timestep - } - - /** - * Toggle seed rendering on/off. - */ - private static void toggleMeshes() { - Spatial.CullHint hint = meshesNode.getLocalCullHint(); - if (hint == Spatial.CullHint.Inherit - || hint == Spatial.CullHint.Never) { - hint = Spatial.CullHint.Always; - } else if (hint == Spatial.CullHint.Always) { - hint = Spatial.CullHint.Never; - } - meshesNode.setCullHint(hint); - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - int numSeeds = meshesNode.getChildren().size(); - boolean isPaused = isPaused(); - String message = String.format("test=%s, count=%d%s", testName, - numSeeds, isPaused ? " PAUSED" : ""); - statusText.setText(message); - } -} +/* + Copyright (c) 2018-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.ConeJoint; +import com.jme3.bullet.joints.HingeJoint; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.joints.Point2PointJoint; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.bullet.joints.SixDofSpringJoint; +import com.jme3.bullet.joints.SliderJoint; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.math.noise.Generator; +import jme3utilities.mesh.Icosphere; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; + +/** + * Test/demonstrate single-ended joints. + *

+ * Seen in the November 2018 demo video: + * https://www.youtube.com/watch?v=Mh9k5AfWzbg + * + * @author Stephen Gold sgold@sonic.net + */ +public class SeJointDemo extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * seed radius (in mesh units) + */ + final private static float seedRadius = 4.8f; + /** + * upper limit on the number of seed groups + */ + final private static int maxGroups = 4; + /** + * upper limit on the number of seeds + */ + final private static int maxSeeds = 300; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(SeJointDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = SeJointDemo.class.getSimpleName(); + /** + * joint axes in physics-space coordinates (unit vector) + */ + final private static Vector3f axisInWorld = Vector3f.UNIT_Z; + // ************************************************************************* + // fields + + /** + * status displayed in the upper-left corner of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + private static CollisionShape seedShape; + /** + * material for each type of seed + */ + final private static Material[] materials = new Material[maxGroups]; + + final private static Matrix3f rotInSeed = new Matrix3f(); + final private static Matrix3f rotInWorld = new Matrix3f(); + /** + * mesh for visualizing seeds + */ + private static Mesh seedMesh; + /** + * scene-graph node for visualizing seeds + */ + final private static Node meshesNode = new Node("meshes node"); + /** + * name of the test that's currently running + */ + private static String testName = "p2p"; + /** + * joint axis in each seed's local coordinates (unit vector) + */ + final private static Vector3f axisInSeed = new Vector3f(); + /** + * gravity vector (in physics-space coordinates) + */ + final private static Vector3f gravity = new Vector3f(); + /** + * offset of pivot relative to each seed (in scaled shape coordinates) + */ + final private static Vector3f pivotInSeed = new Vector3f(); + /** + * pivot location for each group (in physics-space coordinates) + */ + final private static Vector3f[] pivotLocations = new Vector3f[maxGroups]; + /** + * scale factors that determine the seed's shape + */ + final private static Vector3f seedScale = new Vector3f(); + // ************************************************************************* + // constructors + + /** + * Instantiate the SeJointDemo application. + */ + public SeJointDemo() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the SeJointDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new SeJointDemo(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + guiNode.attachChild(statusText); + + super.acorusInit(); + + configureCamera(); + configureDumper(); + configurePhysics(); + configureGroups(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + addLighting(); + + float length = 32f; + attachWorldAxes(length); + + int numRefineSteps = 1; + seedMesh = new Icosphere(numRefineSteps, seedRadius); + seedShape = new MultiSphere(seedRadius); + + meshesNode.setCullHint(Spatial.CullHint.Never); // with meshes visible + rootNode.attachChild(meshesNode); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + super.configureDumper(); + + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.JointsInSpaces, true); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 20f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind("add", KeyInput.KEY_INSERT); + + dim.bind(asDumpScene, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + dim.bind("signal shower", KeyInput.KEY_I); + + dim.bind("test 6dof", KeyInput.KEY_F6); + dim.bind("test 6dofSpring", KeyInput.KEY_F7); + dim.bind("test cone", KeyInput.KEY_F3); + dim.bind("test hinge", KeyInput.KEY_F2); + dim.bind("test new6dof", KeyInput.KEY_F8); + dim.bind("test p2p", KeyInput.KEY_F1); + dim.bind("test slider", KeyInput.KEY_F4); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind("toggle view", KeyInput.KEY_SLASH); + } + + /** + * Process an action that wasn't handled by the active InputMode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "add": + addSeed(); + return; + + case "test 6dof": + cleanupAfterTest(); + testName = "6dof"; + return; + case "test 6dofSpring": + cleanupAfterTest(); + testName = "6dofSpring"; + return; + case "test cone": + cleanupAfterTest(); + testName = "cone"; + return; + case "test hinge": + cleanupAfterTest(); + testName = "hinge"; + return; + case "test new6dof": + cleanupAfterTest(); + testName = "new6dof"; + return; + case "test p2p": + cleanupAfterTest(); + testName = "p2p"; + return; + case "test slider": + cleanupAfterTest(); + testName = "slider"; + return; + + case "toggle view": + toggleMeshes(); + togglePhysicsDebug(); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + statusText.setLocalTranslation(0f, newHeight, 0f); + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + Signals signals = getSignals(); + if (signals.test("shower")) { + addSeed(); + } + + updateStatusText(); + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the main scene. + */ + private void addLighting() { + ColorRGBA ambientColor = new ColorRGBA(2f, 2f, 2f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootNode.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + sun.setName("sun"); + } + + /** + * Add a dynamic rigid body to the scene and PhysicsSpace. + */ + private void addSeed() { + int numSeeds = meshesNode.getChildren().size(); + if (numSeeds >= maxSeeds) { + return; // too many seeds + } + /* + * All seeds share a single spherical mesh + * and a single spherical collision shape. + * Non-uniform scaling gives seeds their shape. + */ + int numGroups = maxGroups; // for most tests + switch (testName) { + case "6dof": + case "6dofSpring": + case "new6dof": + seedScale.set(3f, 1f, 1f); + break; + + case "cone": + numGroups = 1; + seedScale.set(3f, 1f, 1f); + break; + + case "hinge": + seedScale.set(1f, 2f, 1.5f); + break; + + case "p2p": + seedScale.set(1f, 1f, 1f); + break; + + case "slider": + numGroups = 3; + seedScale.set(1f, 2f, 2f); + break; + + default: + throw new IllegalStateException("testName = " + testName); + } + seedShape.setScale(seedScale); + + // Randomize which group the new seed is in. + Generator random = getGenerator(); + int groupIndex = random.nextInt(0, numGroups - 1); + Material material = materials[groupIndex]; + Vector3f pivotInWorld = pivotLocations[groupIndex]; + + // Randomize the new seed's initial location and velocity. + Vector3f velocity = random.nextVector3f(); + Vector3f location = random.nextVector3f(); + location.multLocal(20f, 40f, 20f); + location.addLocal(0f, 160f, 0f); + + Geometry geometry = new Geometry("seed", seedMesh); + meshesNode.attachChild(geometry); + geometry.setMaterial(material); + geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + geometry.move(location); + + RigidBodyControl rbc = new RigidBodyControl(seedShape); + rbc.setApplyScale(true); + rbc.setKinematic(false); + rbc.setLinearDamping(0.5f); + rbc.setLinearVelocity(velocity); + rbc.setPhysicsLocation(location); + rbc.setSleepingThresholds(0f, 0f); // never sleep + + PhysicsJoint joint; + switch (testName) { + case "6dof": + gravity.zero(); + pivotInSeed.set(40f, 0f, 0f); + rotInSeed.loadIdentity(); + float angle = (groupIndex - 1) * FastMath.HALF_PI; + rotInWorld.fromAngleAxis(angle, Vector3f.UNIT_Z); + JointEnd referenceFrame = JointEnd.B; + SixDofJoint sixDofJoint = new SixDofJoint(rbc, pivotInSeed, + pivotInWorld, rotInSeed, rotInWorld, referenceFrame); + sixDofJoint.setAngularLowerLimit(new Vector3f(0f, -1f, -1f)); + sixDofJoint.setAngularUpperLimit(new Vector3f(0f, 1f, 1f)); + joint = sixDofJoint; + break; + + case "6dofSpring": + gravity.zero(); + pivotInSeed.set(40f, 0f, 0f); + rotInSeed.loadIdentity(); + angle = (groupIndex - 1) * FastMath.HALF_PI; + rotInWorld.fromAngleAxis(angle, Vector3f.UNIT_Z); + referenceFrame = JointEnd.B; + SixDofSpringJoint springJoint = new SixDofSpringJoint( + rbc, pivotInSeed, pivotInWorld, rotInSeed, rotInWorld, + referenceFrame); + springJoint.setAngularLowerLimit(new Vector3f(0f, -1f, -1f)); + springJoint.setAngularUpperLimit(new Vector3f(0f, 1f, 1f)); + joint = springJoint; + break; + + case "cone": + gravity.zero(); + pivotInSeed.set(80f, 0f, 0f); + rotInSeed.fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z); + ConeJoint coneJoint + = new ConeJoint(rbc, pivotInSeed, rotInSeed); + coneJoint.setLimit(1f, 1f, 1e30f); // radians + joint = coneJoint; + break; + + case "hinge": + axisInSeed.set(40f, 40f, 0f).normalizeLocal(); + gravity.set(0f, -40f, 0f); + pivotInSeed.set(0f, 40f, 0f); + referenceFrame = JointEnd.A; + joint = new HingeJoint(rbc, pivotInSeed, pivotInWorld, + axisInSeed, axisInWorld, referenceFrame); + break; + + case "new6dof": + gravity.zero(); + pivotInSeed.set(40f, 0f, 0f); + rotInSeed.loadIdentity(); + angle = (groupIndex - 1) * FastMath.HALF_PI; + rotInWorld.fromAngleAxis(angle, Vector3f.UNIT_Z); + New6Dof nJoint = new New6Dof(rbc, pivotInSeed, pivotInWorld, + rotInSeed, rotInWorld, RotationOrder.XYZ); + RotationMotor x = nJoint.getRotationMotor(PhysicsSpace.AXIS_X); + x.set(MotorParam.LowerLimit, 0f); + x.set(MotorParam.UpperLimit, 0f); + RotationMotor y = nJoint.getRotationMotor(PhysicsSpace.AXIS_Y); + y.set(MotorParam.LowerLimit, -1f); + y.set(MotorParam.UpperLimit, 1f); + RotationMotor z = nJoint.getRotationMotor(PhysicsSpace.AXIS_Z); + z.set(MotorParam.LowerLimit, -1f); + z.set(MotorParam.UpperLimit, 1f); + joint = nJoint; + break; + + case "p2p": + gravity.set(0f, -40f, 0f); + pivotInSeed.set(20f, 0f, 0f); + joint = new Point2PointJoint(rbc, pivotInSeed, pivotInWorld); + break; + + case "slider": + gravity.set(0f, 0f, 0f); // TODO + pivotInSeed.set(40f, 0f, 0f); + referenceFrame = JointEnd.B; + joint = new SliderJoint( + rbc, pivotInSeed, pivotInWorld, referenceFrame); + break; + + default: + throw new IllegalStateException("testName = " + testName); + } + + PhysicsSpace physicsSpace = getPhysicsSpace(); + rbc.setPhysicsSpace(physicsSpace); + rbc.setGravity(gravity); // must be set *after* setPhysicsSpace! + + physicsSpace.addJoint(joint); + + geometry.addControl(rbc); + } + + /** + * Clean up after a test. + */ + private void cleanupAfterTest() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + Collection jointList = physicsSpace.getJointList(); + for (PhysicsJoint joint : jointList) { + joint.destroy(); + physicsSpace.removeJoint(joint); + } + + List seeds = meshesNode.getChildren(); + for (Spatial geometry : seeds) { + RigidBodyControl rbc = geometry.getControl(RigidBodyControl.class); + rbc.setPhysicsSpace(null); + geometry.removeControl(rbc); + geometry.removeFromParent(); + } + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.8f; + float far = 4000f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(160f); + flyCam.setZoomSpeed(160f); + + cam.setLocation(new Vector3f(106f, 96.8f, 374.8f)); + cam.setRotation(new Quaternion(0f, 0.9759f, -0.04f, -0.2136f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure seed groups during startup. Seeds are divided into 4 groups, + * each with its own material and pivot location. + */ + private void configureGroups() { + ColorRGBA[] seedColors = new ColorRGBA[maxGroups]; + seedColors[0] = new ColorRGBA(0.2f, 0f, 0f, 1f); + seedColors[1] = new ColorRGBA(0f, 0.07f, 0f, 1f); + seedColors[2] = new ColorRGBA(0f, 0f, 0.3f, 1f); + seedColors[3] = new ColorRGBA(0.2f, 0.1f, 0f, 1f); + for (int i = 0; i < maxGroups; ++i) { + ColorRGBA color = seedColors[i]; + materials[i] = MyAsset.createShinyMaterial(assetManager, color); + materials[i].setFloat("Shininess", 15f); + } + + // The 4 pivot locations are arranged in a square in the X-Y plane. + pivotLocations[0] = new Vector3f(0f, 60f, 0f); + pivotLocations[1] = new Vector3f(-60f, 0f, 0f); + pivotLocations[2] = new Vector3f(0f, -60f, 0f); + pivotLocations[3] = new Vector3f(60f, 0f, 0f); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.setAccuracy(0.1f); // 100-msec timestep + } + + /** + * Toggle seed rendering on/off. + */ + private static void toggleMeshes() { + Spatial.CullHint hint = meshesNode.getLocalCullHint(); + if (hint == Spatial.CullHint.Inherit + || hint == Spatial.CullHint.Never) { + hint = Spatial.CullHint.Always; + } else if (hint == Spatial.CullHint.Always) { + hint = Spatial.CullHint.Never; + } + meshesNode.setCullHint(hint); + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + int numSeeds = meshesNode.getChildren().size(); + boolean isPaused = isPaused(); + String message = String.format("test=%s, count=%d%s", testName, + numSeeds, isPaused ? " PAUSED" : ""); + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemo.java index dc9b63f01..2a155e8cb 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemo.java @@ -1,1195 +1,1195 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.CollisionSpace; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.EmptyShape; -import com.jme3.bullet.collision.shapes.GImpactCollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Triangle; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Line; -import com.jme3.system.AppSettings; -import com.jme3.util.BufferUtils; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.BackingStoreException; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.noise.Generator; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.shape.CompoundTestShapes; -import jme3utilities.minie.test.shape.ShapeGenerator; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; - -/** - * Test/demonstrate splitting of rigid bodies. - *

- * Collision objects are rendered entirely by debug visualization. - * - * @author Stephen Gold sgold@sonic.net - */ -public class SplitDemo - extends PhysicsDemo - implements DebugInitListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(SplitDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = SplitDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * angle between the normal of the splitting plane and default camera's "up" - * vector (in radians, ≥0, <Pi) - */ - private static float splitAngle = 0f; - /** - * visualize the splitting plane - */ - private static Geometry splitterGeometry; - /** - * temporary storage for a 3x3 matrix - */ - final private static Matrix3f tmpMatrix = new Matrix3f(); - /** - * temporary storage for a Quaternion - */ - final private static Quaternion tmpRotation = new Quaternion(); - /** - * AppState to manage the status overlay - */ - private static SplitDemoStatus status; - /** - * first screen location used to define the splitting plane (measured from - * the lower left corner) - */ - final private static Vector2f screen1 = new Vector2f(); - /** - * 2nd screen location used to define the splitting plane (measured from the - * lower left corner) - */ - final private static Vector2f screen2 = new Vector2f(); - /** - * temporary storage for a vector - */ - final private static Vector3f tmpLocation = new Vector3f(); - /** - * first world location used to define the splitting plane - */ - final private static Vector3f world1 = new Vector3f(); - /** - * 2nd world location used to define the splitting plane - */ - final private static Vector3f world2 = new Vector3f(); - // ************************************************************************* - // constructors - - /** - * Instantiate the SplitDemo application. - */ - public SplitDemo() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Count how many rigid bodies are active. - * - * @return the count (≥0) - */ - int countActive() { - int result = 0; - Collection rigidBodies - = getPhysicsSpace().getRigidBodyList(); - for (PhysicsRigidBody rigidBody : rigidBodies) { - if (rigidBody.isActive()) { - ++result; - } - } - - return result; - } - - /** - * Main entry point for the SplitDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Enable direct-memory tracking. - BufferUtils.setTrackDirectMemoryEnabled(true); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - try { - settings.load(applicationName); - } catch (BackingStoreException exception) { - logger.warning("Failed to load AppSettings."); - } - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new SplitDemo(); - application.setSettings(settings); - application.start(); - } - - /** - * Return the inclination angle of the splitting plane. - * - * @return the angle between the plane normal and the default camera's "up" - * vector (in radians, ≥0, <Pi) - */ - static float splitAngle() { - assert splitAngle >= 0f : splitAngle; - assert splitAngle < FastMath.PI : splitAngle; - - return splitAngle; - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - status = new SplitDemoStatus(); - boolean success = stateManager.attach(status); - assert success; - - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - generateShapes(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - renderer.setDefaultAnisotropicFilter(degree); - - Line lineMesh = new Line(Vector3f.ZERO, Vector3f.ZERO); - splitterGeometry = new Geometry("plane", lineMesh); - Material splitter = MyAsset.createWireframeMaterial( - assetManager, ColorRGBA.White); - splitterGeometry.setMaterial(splitter); - rootNode.attachChild(splitterGeometry); - - restartScenario(); - } - - /** - * Calculate screen bounds for the detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes below the status. - float margin = 10f; // in pixels - float leftX = margin; - float topY = viewPortHeight - 88f - margin; - float width = viewPortWidth - leftX - margin; - float height = topY - margin; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - ColorRGBA color = new ColorRGBA(0.2f, 0f, 0f, 1f); - Material solid = MyAsset.createShinyMaterial(assetManager, color); - solid.setFloat("Shininess", 15f); - RenderState additional = solid.getAdditionalRenderState(); - additional.setFaceCullMode(RenderState.FaceCullMode.Off); - registerMaterial("solid", solid); - - ColorRGBA gray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Material stat = MyAsset.createShinyMaterial(assetManager, gray); - stat.setFloat("Shininess", 15f); - additional = stat.getAdditionalRenderState(); - additional.setFaceCullMode(RenderState.FaceCullMode.Off); - registerMaterial("stat", stat); - } - - /** - * Initialize the library of named collision shapes during startup. - */ - @Override - public void generateShapes() { - super.generateShapes(); - CollisionShape shape; - - // "ankh" using manual decomposition - String ankhPath = "CollisionShapes/ankh.j3o"; - shape = (CollisionShape) assetManager.loadAsset(ankhPath); - registerShape("ankh", shape); - - // "banana" using manual decomposition - String bananaPath = "CollisionShapes/banana.j3o"; - shape = (CollisionShape) assetManager.loadAsset(bananaPath); - registerShape("banana", shape); - - // "barrel" - String barrelPath = "CollisionShapes/barrel.j3o"; - shape = (CollisionShape) assetManager.loadAsset(barrelPath); - shape.setScale(3f); - registerShape("barrel", shape); - - // "candyDish" - String candyDishPath = "Models/CandyDish/CandyDish.j3o"; - Node candyDishNode = (Node) assetManager.loadModel(candyDishPath); - Geometry candyDishGeometry = (Geometry) candyDishNode.getChild(0); - Mesh candyDishMesh = candyDishGeometry.getMesh(); - shape = new MeshCollisionShape(candyDishMesh); - shape.setScale(0.5f); - registerShape("candyDish", shape); - - // "duck" using V-HACD - String duckPath = "CollisionShapes/duck.j3o"; - shape = (CollisionShape) assetManager.loadAsset(duckPath); - shape.setScale(2f); - registerShape("duck", shape); - - // "heart" - String heartPath = "CollisionShapes/heart.j3o"; - shape = (CollisionShape) assetManager.loadAsset(heartPath); - shape.setScale(1.2f); - registerShape("heart", shape); - - // "horseshoe" using manual decomposition - String horseshoePath = "CollisionShapes/horseshoe.j3o"; - shape = (CollisionShape) assetManager.loadAsset(horseshoePath); - registerShape("horseshoe", shape); - - // "sword" using V-HACD - String swordPath = "CollisionShapes/sword.j3o"; - shape = (CollisionShape) assetManager.loadAsset(swordPath); - shape.setScale(5f); - registerShape("sword", shape); - - // "teapot" using V-HACD - String teapotPath = "CollisionShapes/teapot.j3o"; - shape = (CollisionShape) assetManager.loadAsset(teapotPath); - shape.setScale(3f); - registerShape("teapot", shape); - - // "teapot" using GImpact - String teapotGiPath = "CollisionShapes/teapotGi.j3o"; - shape = (CollisionShape) assetManager.loadAsset(teapotGiPath); - shape.setScale(3f); - registerShape("teapotGi", shape); - - // letter shapes - for (char character = 'A'; character <= 'Z'; ++character) { - char[] array = {character}; - String glyphString = new String(array); - String assetPath = String.format( - "CollisionShapes/glyphs/%s.j3o", glyphString); - shape = (CollisionShape) assetManager.loadAsset(assetPath); - registerShape(glyphString, shape); - } - - // digit shapes - for (char character = '0'; character <= '9'; ++character) { - char[] array = {character}; - String glyphString = new String(array); - String assetPath = String.format( - "CollisionShapes/glyphs/%s.j3o", glyphString); - shape = (CollisionShape) assetManager.loadAsset(assetPath); - registerShape(glyphString, shape); - } - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind("make splittable", KeyInput.KEY_TAB); - - dim.bind("next field", KeyInput.KEY_NUMPAD2); - dim.bind("next value", KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); - - dim.bind("previous field", KeyInput.KEY_NUMPAD8); - dim.bind("previous value", KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); - - dim.bind("restart", KeyInput.KEY_NUMPAD5); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - dim.bindSignal("rotatePlaneCcw", KeyInput.KEY_LBRACKET); - dim.bindSignal("rotatePlaneCw", KeyInput.KEY_RBRACKET); - - dim.bind("split", KeyInput.KEY_RETURN, KeyInput.KEY_INSERT, - KeyInput.KEY_NUMPAD0, KeyInput.KEY_SPACE); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind("toggle childColor", KeyInput.KEY_COMMA); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind("toggle wireframe", KeyInput.KEY_SLASH); - - dim.bind("value+7", KeyInput.KEY_NUMPAD9); - dim.bind("value-7", KeyInput.KEY_NUMPAD7); - } - - /** - * Process an action that wasn't handled by the active InputMode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "make splittable": - makeSplittableAll(); - return; - - case "next field": - status.advanceSelectedField(+1); - return; - case "next value": - status.advanceValue(+1); - return; - case "previous field": - status.advanceSelectedField(-1); - return; - case "previous value": - status.advanceValue(-1); - return; - - case "restart": - restartScenario(); - return; - case "split": - splitAll(); // TODO do this on the physics thread - return; - - case "toggle childColor": - status.toggleChildColor(); - setDebugMaterialsAll(); - return; - case "toggle wireframe": - status.toggleWireframe(); - setDebugMaterialsAll(); - return; - - case "value+7": - status.advanceValue(+7); - return; - case "value-7": - status.advanceValue(-7); - return; - - default: - } - } - - // The action is not handled: forward it to the superclass. - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - status.resize(newWidth, newHeight); - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked after adding a collision object to the PhysicsSpace. - * - * @param pco the object that was added (not null) - */ - @Override - public void postAdd(PhysicsCollisionObject pco) { - pco.setDebugMeshResolution(DebugShapeFactory.highResolution); - setDebugMaterial(pco); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - Signals signals = getSignals(); - if (signals.test("rotatePlaneCcw")) { - splitAngle += tpf; - } - if (signals.test("rotatePlaneCw")) { - splitAngle -= tpf; - } - splitAngle = MyMath.modulo(splitAngle, FastMath.PI); - - float w = cam.getWidth(); - float h = cam.getHeight(); - if (Math.abs(splitAngle - FastMath.HALF_PI) < FastMath.QUARTER_PI) { - // The plane is more vertical than horizontal. - float cotangent = FastMath.tan(FastMath.HALF_PI - splitAngle); - screen1.x = 0.5f * (w + h * cotangent); - screen1.y = h; - screen2.x = 0.5f * (w - h * cotangent); - screen2.y = 0f; - } else { // The plane is more horizontal than vertical. - float tangent = FastMath.tan(splitAngle); - screen1.x = w; - screen1.y = 0.5f * (h + w * tangent); - screen2.x = 0f; - screen2.y = 0.5f * (h - w * tangent); - } - cam.getWorldCoordinates(screen1, 0.1f, world1); - cam.getWorldCoordinates(screen2, 0.1f, world2); - - Line planeMesh = (Line) splitterGeometry.getMesh(); - planeMesh.updatePoints(world1, world2); - splitterGeometry.setMesh(planeMesh); // to update the geometry's bounds - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private static void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); - Vector3f direction = new Vector3f(1f, -3f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - rootSpatial.addLight(sun); - sun.setName("sun"); - } - - /** - * Create a rigid body with the specified shape and debug normals and add it - * to the PhysicsSpace at the origin, with random rotation. - * - * @param shape the collision shape to use (not null) - * @param debugMeshNormals how to generate normals for debug visualization - * (not null) - * @param mass the desired mass (0 or 1) - */ - private void addRigidBody( - CollisionShape shape, MeshNormals debugMeshNormals, float mass) { - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setDebugMeshNormals(debugMeshNormals); - - Generator random = getGenerator(); - random.nextVector3f(tmpLocation); - body.setPhysicsLocation(tmpLocation); - random.nextQuaternion(tmpRotation); - if (!(shape instanceof HeightfieldCollisionShape)) { - body.setPhysicsRotation(tmpRotation); - } - - addCollisionObject(body); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.1f; - float far = 500f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(10f); - flyCam.setZoomSpeed(10f); - - cam.setLocation(new Vector3f(0f, 0f, 6.8f)); - cam.setRotation(new Quaternion(0f, 1f, 0f, 0f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - PhysicsBody.setDeactivationEnabled(false); // avoid a distraction - - bulletAppState = new SoftPhysicsAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugInitListener(this); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.setGravity(Vector3f.ZERO); - } - - /** - * Ensure that all rigid bodies in the PhysicsSpace have splittable shapes. - */ - private void makeSplittableAll() { - PhysicsSpace space = getPhysicsSpace(); - Collection allPcos = space.getPcoList(); - for (PhysicsCollisionObject pco : allPcos) { - PhysicsRigidBody body = (PhysicsRigidBody) pco; - makeSplittable(body); - } - } - - /** - * Ensure that the specified rigid body has a splittable shape. - * - * @param body (not null) - */ - private static void makeSplittable(PhysicsRigidBody body) { - CollisionShape oldShape = body.getCollisionShape(); - CollisionShape splittableShape = oldShape.toSplittableShape(); - assert splittableShape.canSplit(); - body.setCollisionShape(splittableShape); - } - - /** - * Pseudo-randomly select the shape of a decimal digit. - * - * @return the pre-existing instance (not null) - */ - private CollisionShape randomDigit() { - Random random = getGenerator(); - char glyphChar = (char) ('0' + random.nextInt(10)); - String glyphString = Character.toString(glyphChar); - CollisionShape result = findShape(glyphString); - assert result != null : glyphChar; - - return result; - } - - /** - * Pseudo-randomly select the shape of an uppercase letter. - * - * @return the pre-existing instance (not null) - */ - private CollisionShape randomLetter() { - Random random = getGenerator(); - char glyphChar = (char) ('A' + random.nextInt(26)); - String glyphString = Character.toString(glyphChar); - CollisionShape result = findShape(glyphString); - assert result != null : glyphChar; - - return result; - } - - /** - * Pseudo-randomly generate an asymmetrical compound shape consisting of 2 - * cylinders, a head and a handle. - * - * @return a new instance (not null) - */ - private CollisionShape randomMallet() { - Generator random = getGenerator(); - float handleR = 0.5f; - float headR = handleR + random.nextFloat(); - float headHalfLength = headR + random.nextFloat(); - float handleHalfLength = headHalfLength + random.nextFloat(0f, 2.5f); - CompoundCollisionShape result = CompoundTestShapes.makeMadMallet( - handleR, headR, handleHalfLength, headHalfLength); - - return result; - } - - /** - * Restart the current scenario. - */ - private void restartScenario() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.destroy(); - assert physicsSpace.isEmpty(); - - setUpShape(); - } - - /** - * Update the debug materials of the specified collision object. - * - * @param pco the object to update (not null, modified) - */ - private void setDebugMaterial(PhysicsCollisionObject pco) { - CollisionShape shape = pco.getCollisionShape(); - - Material debugMaterial; - if (status.isWireframe()) { - debugMaterial = null; - - } else if (status.isChildColoring() - && shape instanceof CompoundCollisionShape) { - debugMaterial = BulletDebugAppState.enableChildColoring; - - } else if (pco instanceof PhysicsRigidBody && pco.isStatic()) { - // Use the shiny gray lit/shaded material for static bodies. - debugMaterial = findMaterial("stat"); - - } else { // Use the shiny red lit/shaded material. - debugMaterial = findMaterial("solid"); - } - - pco.setDebugMaterial(debugMaterial); - } - - /** - * Update the debug materials of all collision objects. - */ - private void setDebugMaterialsAll() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { - setDebugMaterial(pco); - } - } - - /** - * Add a dynamic rigid body with the selected shape. - */ - private void setUpShape() { - ShapeGenerator random = getGenerator(); - float randomMass = random.nextInt(2); - String shapeName = status.shapeName(); - - CollisionShape shape; - switch (shapeName) { - case "ankh": - case "chair": - case "duck": - case "heart": - case "horseshoe": - case "sword": - case "table": - case "teapot": - case "thumbTack": - case "tray": - shape = findShape(shapeName); - addRigidBody(shape, MeshNormals.Facet, randomMass); - break; - - case "banana": - case "barbell": - case "barrel": - case "bowl": - case "knucklebone": - case "ladder": - case "link": - case "teapotGi": - case "top": - shape = findShape(shapeName); - addRigidBody(shape, MeshNormals.Smooth, randomMass); - break; - - case "bedOfNails": - case "corner": - case "roundedRectangle": - case "triangle": - shape = findShape(shapeName); - addRigidBody( - shape, MeshNormals.Facet, PhysicsBody.massForStatic); - break; - - case "box": - case "frame": - case "halfPipe": - case "hull": - case "iBeam": - case "lidlessBox": - case "platonic": - case "prism": - case "pyramid": - case "star": - case "tetrahedron": - case "triangularFrame": - case "trident": - case "washer": - shape = random.nextShape(shapeName); - addRigidBody(shape, MeshNormals.Facet, randomMass); - break; - - case "candyDish": - case "dimples": - case "smooth": - shape = findShape(shapeName); - addRigidBody( - shape, MeshNormals.Smooth, PhysicsBody.massForStatic); - break; - - case "capsule": - case "cone": - case "coneBox": - case "cylinder": - case "cylinderBox": - case "dome": - case "football": - case "multiSphere": - case "roundedDisc": - case "saucer": - case "snowman": - case "torus": - shape = random.nextShape(shapeName); - addRigidBody(shape, MeshNormals.Smooth, randomMass); - break; - - case "digit": - shape = randomDigit(); - shape.setScale(0.5f); - addRigidBody(shape, MeshNormals.Facet, randomMass); - break; - - case "letter": - shape = randomLetter(); - shape.setScale(0.5f); - addRigidBody(shape, MeshNormals.Facet, randomMass); - break; - - case "mallet": - shape = randomMallet(); - addRigidBody(shape, MeshNormals.Smooth, randomMass); - break; - - case "sphere": - shape = random.nextShape(shapeName); - addRigidBody(shape, MeshNormals.Sphere, randomMass); - break; - - default: - String message = "shapeName = " + MyString.quote(shapeName); - throw new RuntimeException(message); - } - } - - /** - * Split all rigid bodies in the PhysicsSpace. - */ - private void splitAll() { - Vector3f world3 = cam.getLocation(); // alias - Triangle triangle = new Triangle(world1, world2, world3); - - PhysicsSpace space = getPhysicsSpace(); - Collection allPcos = space.getPcoList(); - for (PhysicsCollisionObject pco : allPcos) { - PhysicsRigidBody body = (PhysicsRigidBody) pco; - splitBody(body, triangle); - } - } - - /** - * Attempt to split the specified rigid body using the plane of the - * specified triangle. - * - * @param oldBody (not null, added to the PhysicsSpace) - * @param worldTriangle a triangle that defines the splitting plane (in - * world coordinates, not null, unaffected) - */ - private void splitBody(PhysicsRigidBody oldBody, Triangle worldTriangle) { - CollisionShape originalShape = oldBody.getCollisionShape(); - CollisionShape splittableShape = originalShape.toSplittableShape(); - assert splittableShape.canSplit(); - if (splittableShape instanceof EmptyShape) { - return; // Splitting an empty shape has no effect. - } - - // Transform the triangle to the shape coordinate system. - Transform shapeToWorld = oldBody.getTransform(null); - if (splittableShape instanceof CompoundCollisionShape) { - shapeToWorld.setScale(1f); - } else { - splittableShape.getScale(shapeToWorld.getScale()); - } - Triangle shapeTriangle - = MyMath.transformInverse(shapeToWorld, worldTriangle, null); - - CollisionShape[] shapes; - float[] volumes; - int[] signs; - Vector3f[] locations; - Vector3f worldNormal = worldTriangle.getNormal(); // alias - - if (splittableShape instanceof HullCollisionShape) { - HullCollisionShape hullShape = (HullCollisionShape) splittableShape; - ChildCollisionShape[] children = hullShape.split(shapeTriangle); - assert children.length == 2 : children.length; - if (children[0] == null || children[1] == null) { - return; // The split plane didn't intersect the hull. - } - - shapes = new CollisionShape[2]; - volumes = new float[2]; - signs = new int[2]; - locations = new Vector3f[2]; - for (int sideI = 0; sideI < 2; ++sideI) { - shapes[sideI] = children[sideI].getShape(); - volumes[sideI] = shapes[sideI].scaledVolume(); - signs[sideI] = 2 * sideI - 1; - - Vector3f location = children[sideI].copyOffset(null); - MyMath.transform(shapeToWorld, location, location); - locations[sideI] = location; - } - - } else if (splittableShape instanceof CompoundCollisionShape) { - CompoundCollisionShape compound - = (CompoundCollisionShape) splittableShape; - CompoundCollisionShape[] compounds = compound.split(shapeTriangle); - assert compounds.length == 2 : compounds.length; - if (compounds[0] == null || compounds[1] == null) { - return; // The split plane didn't intersect the compound shape. - } - /* - * Enumerate the groups of connected children - * on each side of the split plane. - */ - List groupList = new ArrayList<>(4); - List signList = new ArrayList<>(4); - CollisionSpace testSpace = getPhysicsSpace(); - for (int sideI = 0; sideI < 2; ++sideI) { - ChildCollisionShape[] children - = compounds[sideI].listChildren(); - int numChildren = children.length; - int[] map = new int[numChildren]; - int numGroups = compounds[sideI].countGroups(testSpace, map); - int sign = 2 * sideI - 1; - - for (int groupI = 0; groupI < numGroups; ++groupI) { - CompoundCollisionShape newGroup - = new CompoundCollisionShape(numChildren); - for (int childI = 0; childI < numChildren; ++childI) { - if (map[childI] == groupI) { - ChildCollisionShape child = children[childI]; - CollisionShape baseShape = child.getShape(); - child.copyOffset(tmpLocation); - child.copyRotationMatrix(tmpMatrix); - newGroup.addChildShape( - baseShape, tmpLocation, tmpMatrix); - } - } - groupList.add(newGroup); - signList.add(sign); - } - } - - int numGroups = groupList.size(); - assert signList.size() == numGroups; - shapes = new CollisionShape[numGroups]; - volumes = new float[numGroups]; - signs = new int[numGroups]; - locations = new Vector3f[numGroups]; - - for (int groupI = 0; groupI < numGroups; ++groupI) { - CompoundCollisionShape shape = groupList.get(groupI); - shapes[groupI] = shape; - volumes[groupI] = shape.scaledVolume(); - signs[groupI] = signList.get(groupI); - /* - * Translate each compound so its AABB is centered at (0,0,0) - * in its shape coordinates. - */ - Vector3f location = shape.aabbCenter(null); - Vector3f offset = location.negate(); - shape.translate(offset); - shapeToWorld.setScale(1f); - MyMath.transform(shapeToWorld, location, location); - locations[groupI] = location; - } - - } else if (splittableShape instanceof GImpactCollisionShape) { - GImpactCollisionShape gi = (GImpactCollisionShape) splittableShape; - ChildCollisionShape[] children = gi.split(shapeTriangle); - assert children.length == 2 : children.length; - if (children[0] == null || children[1] == null) { - return; // The split plane didn't intersect the GImpact shape. - } - - shapes = new CollisionShape[2]; - volumes = new float[2]; - signs = new int[2]; - locations = new Vector3f[2]; - for (int sideI = 0; sideI < 2; ++sideI) { - shapes[sideI] = children[sideI].getShape(); - volumes[sideI] = 1f; // TODO calculate area - signs[sideI] = 2 * sideI - 1; - - Vector3f location = children[sideI].copyOffset(null); - MyMath.transform(shapeToWorld, location, location); - locations[sideI] = location; - } - - } else if (splittableShape instanceof MeshCollisionShape) { - assert oldBody.isStatic(); - MeshCollisionShape mesh = (MeshCollisionShape) splittableShape; - shapes = mesh.split(shapeTriangle); - assert shapes.length == 2 : shapes.length; - if (shapes[0] == null || shapes[1] == null) { - return; // The split plane didn't intersect the mesh shape. - } - - volumes = new float[2]; - signs = new int[2]; - locations = new Vector3f[2]; - for (int sideI = 0; sideI < 2; ++sideI) { - volumes[sideI] = 0f; // unused - signs[sideI] = 2 * sideI - 1; - locations[sideI] = oldBody.getPhysicsLocation(null); - } - - } else { // TODO handle simplex n<=2 - logger.log(Level.WARNING, "Shape not split: {0}", originalShape); - return; - } - - splitBody(oldBody, worldNormal, shapes, volumes, signs, locations); - } - - /** - * Split the specified rigid body using the specified shapes. - * - * @param oldBody (not null, added to the PhysicsSpace) - * @param worldNormal the normal of the splitting plane (in world - * coordinates, not null, unaffected) - * @param shapes the shapes to use (length≥2, all not null) - * @param sizes the estimated relative size of each shape (all >0) - * @param signs -1 → shape is on the negative side of the splitting - * plane, +1 → on the positive side - * @param locations the center location of each shape (in physics-space - * coordinates, all not null) - */ - private void splitBody(PhysicsRigidBody oldBody, Vector3f worldNormal, - CollisionShape[] shapes, float[] sizes, int[] signs, - Vector3f[] locations) { - int numShapes = shapes.length; - assert numShapes >= 2 : numShapes; - assert sizes.length == numShapes : sizes.length; - assert signs.length == numShapes : signs.length; - assert locations.length == numShapes : locations.length; - - // Tweak the locations to create some separation. - boolean isDynamic = oldBody.isDynamic(); - float deltaX = isDynamic ? 0.04f : 0.1f; - for (int shapeI = 0; shapeI < numShapes; ++shapeI) { - float factor = signs[shapeI] * deltaX; - MyVector3f.accumulateScaled(locations[shapeI], worldNormal, factor); - } - - float[] masses = new float[numShapes]; - Vector3f v; - Vector3f w; - Vector3f[] velocities = new Vector3f[numShapes]; - if (isDynamic) { - // Tweak the linear velocities to enhance the separation. - float deltaV = 0.04f; - v = oldBody.getLinearVelocity(null); - for (int shapeI = 0; shapeI < numShapes; ++shapeI) { - velocities[shapeI] = v.clone(); - float multiplier = signs[shapeI] * deltaV; - MyVector3f.accumulateScaled( - velocities[shapeI], worldNormal, multiplier); - } - - // Calculate the masses. - float totalSize = 0f; - for (int shapeI = 0; shapeI < numShapes; ++shapeI) { - totalSize += sizes[shapeI]; - } - assert totalSize > 0f : totalSize; - float totalMass = oldBody.getMass(); - for (int shapeI = 0; shapeI < numShapes; ++shapeI) { - masses[shapeI] = totalMass * sizes[shapeI] / totalSize; - assert masses[shapeI] > 0f : masses[shapeI]; - } - - w = oldBody.getAngularVelocity(null); - - } else { - for (int shapeI = 0; shapeI < numShapes; ++shapeI) { - masses[shapeI] = PhysicsBody.massForStatic; - } - w = null; - } - - MeshNormals debugMeshNormals = oldBody.debugMeshNormals(); - Quaternion orientation = oldBody.getPhysicsRotation(null); - - PhysicsSpace space = getPhysicsSpace(); - space.removeCollisionObject(oldBody); - - for (int shapeI = 0; shapeI < numShapes; ++shapeI) { - PhysicsRigidBody body - = new PhysicsRigidBody(shapes[shapeI], masses[shapeI]); - body.setDebugMeshNormals(debugMeshNormals); - body.setPhysicsLocation(locations[shapeI]); - body.setPhysicsRotation(orientation); - if (isDynamic) { - body.setAngularVelocity(w); - body.setLinearVelocity(velocities[shapeI]); - } - addCollisionObject(body); - } - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.CollisionSpace; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.EmptyShape; +import com.jme3.bullet.collision.shapes.GImpactCollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Triangle; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Line; +import com.jme3.system.AppSettings; +import com.jme3.util.BufferUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.BackingStoreException; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.noise.Generator; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.shape.CompoundTestShapes; +import jme3utilities.minie.test.shape.ShapeGenerator; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; + +/** + * Test/demonstrate splitting of rigid bodies. + *

+ * Collision objects are rendered entirely by debug visualization. + * + * @author Stephen Gold sgold@sonic.net + */ +public class SplitDemo + extends PhysicsDemo + implements DebugInitListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(SplitDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = SplitDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * angle between the normal of the splitting plane and default camera's "up" + * vector (in radians, ≥0, <Pi) + */ + private static float splitAngle = 0f; + /** + * visualize the splitting plane + */ + private static Geometry splitterGeometry; + /** + * temporary storage for a 3x3 matrix + */ + final private static Matrix3f tmpMatrix = new Matrix3f(); + /** + * temporary storage for a Quaternion + */ + final private static Quaternion tmpRotation = new Quaternion(); + /** + * AppState to manage the status overlay + */ + private static SplitDemoStatus status; + /** + * first screen location used to define the splitting plane (measured from + * the lower left corner) + */ + final private static Vector2f screen1 = new Vector2f(); + /** + * 2nd screen location used to define the splitting plane (measured from the + * lower left corner) + */ + final private static Vector2f screen2 = new Vector2f(); + /** + * temporary storage for a vector + */ + final private static Vector3f tmpLocation = new Vector3f(); + /** + * first world location used to define the splitting plane + */ + final private static Vector3f world1 = new Vector3f(); + /** + * 2nd world location used to define the splitting plane + */ + final private static Vector3f world2 = new Vector3f(); + // ************************************************************************* + // constructors + + /** + * Instantiate the SplitDemo application. + */ + public SplitDemo() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Count how many rigid bodies are active. + * + * @return the count (≥0) + */ + int countActive() { + int result = 0; + Collection rigidBodies + = getPhysicsSpace().getRigidBodyList(); + for (PhysicsRigidBody rigidBody : rigidBodies) { + if (rigidBody.isActive()) { + ++result; + } + } + + return result; + } + + /** + * Main entry point for the SplitDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Enable direct-memory tracking. + BufferUtils.setTrackDirectMemoryEnabled(true); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + try { + settings.load(applicationName); + } catch (BackingStoreException exception) { + logger.warning("Failed to load AppSettings."); + } + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new SplitDemo(); + application.setSettings(settings); + application.start(); + } + + /** + * Return the inclination angle of the splitting plane. + * + * @return the angle between the plane normal and the default camera's "up" + * vector (in radians, ≥0, <Pi) + */ + static float splitAngle() { + assert splitAngle >= 0f : splitAngle; + assert splitAngle < FastMath.PI : splitAngle; + + return splitAngle; + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + status = new SplitDemoStatus(); + boolean success = stateManager.attach(status); + assert success; + + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + generateShapes(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + renderer.setDefaultAnisotropicFilter(degree); + + Line lineMesh = new Line(Vector3f.ZERO, Vector3f.ZERO); + splitterGeometry = new Geometry("plane", lineMesh); + Material splitter = MyAsset.createWireframeMaterial( + assetManager, ColorRGBA.White); + splitterGeometry.setMaterial(splitter); + rootNode.attachChild(splitterGeometry); + + restartScenario(); + } + + /** + * Calculate screen bounds for the detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes below the status. + float margin = 10f; // in pixels + float leftX = margin; + float topY = viewPortHeight - 88f - margin; + float width = viewPortWidth - leftX - margin; + float height = topY - margin; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + ColorRGBA color = new ColorRGBA(0.2f, 0f, 0f, 1f); + Material solid = MyAsset.createShinyMaterial(assetManager, color); + solid.setFloat("Shininess", 15f); + RenderState additional = solid.getAdditionalRenderState(); + additional.setFaceCullMode(RenderState.FaceCullMode.Off); + registerMaterial("solid", solid); + + ColorRGBA gray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Material stat = MyAsset.createShinyMaterial(assetManager, gray); + stat.setFloat("Shininess", 15f); + additional = stat.getAdditionalRenderState(); + additional.setFaceCullMode(RenderState.FaceCullMode.Off); + registerMaterial("stat", stat); + } + + /** + * Initialize the library of named collision shapes during startup. + */ + @Override + public void generateShapes() { + super.generateShapes(); + CollisionShape shape; + + // "ankh" using manual decomposition + String ankhPath = "CollisionShapes/ankh.j3o"; + shape = (CollisionShape) assetManager.loadAsset(ankhPath); + registerShape("ankh", shape); + + // "banana" using manual decomposition + String bananaPath = "CollisionShapes/banana.j3o"; + shape = (CollisionShape) assetManager.loadAsset(bananaPath); + registerShape("banana", shape); + + // "barrel" + String barrelPath = "CollisionShapes/barrel.j3o"; + shape = (CollisionShape) assetManager.loadAsset(barrelPath); + shape.setScale(3f); + registerShape("barrel", shape); + + // "candyDish" + String candyDishPath = "Models/CandyDish/CandyDish.j3o"; + Node candyDishNode = (Node) assetManager.loadModel(candyDishPath); + Geometry candyDishGeometry = (Geometry) candyDishNode.getChild(0); + Mesh candyDishMesh = candyDishGeometry.getMesh(); + shape = new MeshCollisionShape(candyDishMesh); + shape.setScale(0.5f); + registerShape("candyDish", shape); + + // "duck" using V-HACD + String duckPath = "CollisionShapes/duck.j3o"; + shape = (CollisionShape) assetManager.loadAsset(duckPath); + shape.setScale(2f); + registerShape("duck", shape); + + // "heart" + String heartPath = "CollisionShapes/heart.j3o"; + shape = (CollisionShape) assetManager.loadAsset(heartPath); + shape.setScale(1.2f); + registerShape("heart", shape); + + // "horseshoe" using manual decomposition + String horseshoePath = "CollisionShapes/horseshoe.j3o"; + shape = (CollisionShape) assetManager.loadAsset(horseshoePath); + registerShape("horseshoe", shape); + + // "sword" using V-HACD + String swordPath = "CollisionShapes/sword.j3o"; + shape = (CollisionShape) assetManager.loadAsset(swordPath); + shape.setScale(5f); + registerShape("sword", shape); + + // "teapot" using V-HACD + String teapotPath = "CollisionShapes/teapot.j3o"; + shape = (CollisionShape) assetManager.loadAsset(teapotPath); + shape.setScale(3f); + registerShape("teapot", shape); + + // "teapot" using GImpact + String teapotGiPath = "CollisionShapes/teapotGi.j3o"; + shape = (CollisionShape) assetManager.loadAsset(teapotGiPath); + shape.setScale(3f); + registerShape("teapotGi", shape); + + // letter shapes + for (char character = 'A'; character <= 'Z'; ++character) { + char[] array = {character}; + String glyphString = new String(array); + String assetPath = String.format( + "CollisionShapes/glyphs/%s.j3o", glyphString); + shape = (CollisionShape) assetManager.loadAsset(assetPath); + registerShape(glyphString, shape); + } + + // digit shapes + for (char character = '0'; character <= '9'; ++character) { + char[] array = {character}; + String glyphString = new String(array); + String assetPath = String.format( + "CollisionShapes/glyphs/%s.j3o", glyphString); + shape = (CollisionShape) assetManager.loadAsset(assetPath); + registerShape(glyphString, shape); + } + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind("make splittable", KeyInput.KEY_TAB); + + dim.bind("next field", KeyInput.KEY_NUMPAD2); + dim.bind("next value", KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); + + dim.bind("previous field", KeyInput.KEY_NUMPAD8); + dim.bind("previous value", KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); + + dim.bind("restart", KeyInput.KEY_NUMPAD5); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + dim.bindSignal("rotatePlaneCcw", KeyInput.KEY_LBRACKET); + dim.bindSignal("rotatePlaneCw", KeyInput.KEY_RBRACKET); + + dim.bind("split", KeyInput.KEY_RETURN, KeyInput.KEY_INSERT, + KeyInput.KEY_NUMPAD0, KeyInput.KEY_SPACE); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind("toggle childColor", KeyInput.KEY_COMMA); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind("toggle wireframe", KeyInput.KEY_SLASH); + + dim.bind("value+7", KeyInput.KEY_NUMPAD9); + dim.bind("value-7", KeyInput.KEY_NUMPAD7); + } + + /** + * Process an action that wasn't handled by the active InputMode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "make splittable": + makeSplittableAll(); + return; + + case "next field": + status.advanceSelectedField(+1); + return; + case "next value": + status.advanceValue(+1); + return; + case "previous field": + status.advanceSelectedField(-1); + return; + case "previous value": + status.advanceValue(-1); + return; + + case "restart": + restartScenario(); + return; + case "split": + splitAll(); // TODO do this on the physics thread + return; + + case "toggle childColor": + status.toggleChildColor(); + setDebugMaterialsAll(); + return; + case "toggle wireframe": + status.toggleWireframe(); + setDebugMaterialsAll(); + return; + + case "value+7": + status.advanceValue(+7); + return; + case "value-7": + status.advanceValue(-7); + return; + + default: + } + } + + // The action is not handled: forward it to the superclass. + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + status.resize(newWidth, newHeight); + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked after adding a collision object to the PhysicsSpace. + * + * @param pco the object that was added (not null) + */ + @Override + public void postAdd(PhysicsCollisionObject pco) { + pco.setDebugMeshResolution(DebugShapeFactory.highResolution); + setDebugMaterial(pco); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + Signals signals = getSignals(); + if (signals.test("rotatePlaneCcw")) { + splitAngle += tpf; + } + if (signals.test("rotatePlaneCw")) { + splitAngle -= tpf; + } + splitAngle = MyMath.modulo(splitAngle, FastMath.PI); + + float w = cam.getWidth(); + float h = cam.getHeight(); + if (Math.abs(splitAngle - FastMath.HALF_PI) < FastMath.QUARTER_PI) { + // The plane is more vertical than horizontal. + float cotangent = FastMath.tan(FastMath.HALF_PI - splitAngle); + screen1.x = 0.5f * (w + h * cotangent); + screen1.y = h; + screen2.x = 0.5f * (w - h * cotangent); + screen2.y = 0f; + } else { // The plane is more horizontal than vertical. + float tangent = FastMath.tan(splitAngle); + screen1.x = w; + screen1.y = 0.5f * (h + w * tangent); + screen2.x = 0f; + screen2.y = 0.5f * (h - w * tangent); + } + cam.getWorldCoordinates(screen1, 0.1f, world1); + cam.getWorldCoordinates(screen2, 0.1f, world2); + + Line planeMesh = (Line) splitterGeometry.getMesh(); + planeMesh.updatePoints(world1, world2); + splitterGeometry.setMesh(planeMesh); // to update the geometry's bounds + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private static void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); + Vector3f direction = new Vector3f(1f, -3f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + rootSpatial.addLight(sun); + sun.setName("sun"); + } + + /** + * Create a rigid body with the specified shape and debug normals and add it + * to the PhysicsSpace at the origin, with random rotation. + * + * @param shape the collision shape to use (not null) + * @param debugMeshNormals how to generate normals for debug visualization + * (not null) + * @param mass the desired mass (0 or 1) + */ + private void addRigidBody( + CollisionShape shape, MeshNormals debugMeshNormals, float mass) { + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setDebugMeshNormals(debugMeshNormals); + + Generator random = getGenerator(); + random.nextVector3f(tmpLocation); + body.setPhysicsLocation(tmpLocation); + random.nextQuaternion(tmpRotation); + if (!(shape instanceof HeightfieldCollisionShape)) { + body.setPhysicsRotation(tmpRotation); + } + + addCollisionObject(body); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.1f; + float far = 500f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(10f); + flyCam.setZoomSpeed(10f); + + cam.setLocation(new Vector3f(0f, 0f, 6.8f)); + cam.setRotation(new Quaternion(0f, 1f, 0f, 0f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + PhysicsBody.setDeactivationEnabled(false); // avoid a distraction + + bulletAppState = new SoftPhysicsAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugInitListener(this); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.setGravity(Vector3f.ZERO); + } + + /** + * Ensure that all rigid bodies in the PhysicsSpace have splittable shapes. + */ + private void makeSplittableAll() { + PhysicsSpace space = getPhysicsSpace(); + Collection allPcos = space.getPcoList(); + for (PhysicsCollisionObject pco : allPcos) { + PhysicsRigidBody body = (PhysicsRigidBody) pco; + makeSplittable(body); + } + } + + /** + * Ensure that the specified rigid body has a splittable shape. + * + * @param body (not null) + */ + private static void makeSplittable(PhysicsRigidBody body) { + CollisionShape oldShape = body.getCollisionShape(); + CollisionShape splittableShape = oldShape.toSplittableShape(); + assert splittableShape.canSplit(); + body.setCollisionShape(splittableShape); + } + + /** + * Pseudo-randomly select the shape of a decimal digit. + * + * @return the pre-existing instance (not null) + */ + private CollisionShape randomDigit() { + Random random = getGenerator(); + char glyphChar = (char) ('0' + random.nextInt(10)); + String glyphString = Character.toString(glyphChar); + CollisionShape result = findShape(glyphString); + assert result != null : glyphChar; + + return result; + } + + /** + * Pseudo-randomly select the shape of an uppercase letter. + * + * @return the pre-existing instance (not null) + */ + private CollisionShape randomLetter() { + Random random = getGenerator(); + char glyphChar = (char) ('A' + random.nextInt(26)); + String glyphString = Character.toString(glyphChar); + CollisionShape result = findShape(glyphString); + assert result != null : glyphChar; + + return result; + } + + /** + * Pseudo-randomly generate an asymmetrical compound shape consisting of 2 + * cylinders, a head and a handle. + * + * @return a new instance (not null) + */ + private CollisionShape randomMallet() { + Generator random = getGenerator(); + float handleR = 0.5f; + float headR = handleR + random.nextFloat(); + float headHalfLength = headR + random.nextFloat(); + float handleHalfLength = headHalfLength + random.nextFloat(0f, 2.5f); + CompoundCollisionShape result = CompoundTestShapes.makeMadMallet( + handleR, headR, handleHalfLength, headHalfLength); + + return result; + } + + /** + * Restart the current scenario. + */ + private void restartScenario() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.destroy(); + assert physicsSpace.isEmpty(); + + setUpShape(); + } + + /** + * Update the debug materials of the specified collision object. + * + * @param pco the object to update (not null, modified) + */ + private void setDebugMaterial(PhysicsCollisionObject pco) { + CollisionShape shape = pco.getCollisionShape(); + + Material debugMaterial; + if (status.isWireframe()) { + debugMaterial = null; + + } else if (status.isChildColoring() + && shape instanceof CompoundCollisionShape) { + debugMaterial = BulletDebugAppState.enableChildColoring; + + } else if (pco instanceof PhysicsRigidBody && pco.isStatic()) { + // Use the shiny gray lit/shaded material for static bodies. + debugMaterial = findMaterial("stat"); + + } else { // Use the shiny red lit/shaded material. + debugMaterial = findMaterial("solid"); + } + + pco.setDebugMaterial(debugMaterial); + } + + /** + * Update the debug materials of all collision objects. + */ + private void setDebugMaterialsAll() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { + setDebugMaterial(pco); + } + } + + /** + * Add a dynamic rigid body with the selected shape. + */ + private void setUpShape() { + ShapeGenerator random = getGenerator(); + float randomMass = random.nextInt(2); + String shapeName = status.shapeName(); + + CollisionShape shape; + switch (shapeName) { + case "ankh": + case "chair": + case "duck": + case "heart": + case "horseshoe": + case "sword": + case "table": + case "teapot": + case "thumbTack": + case "tray": + shape = findShape(shapeName); + addRigidBody(shape, MeshNormals.Facet, randomMass); + break; + + case "banana": + case "barbell": + case "barrel": + case "bowl": + case "knucklebone": + case "ladder": + case "link": + case "teapotGi": + case "top": + shape = findShape(shapeName); + addRigidBody(shape, MeshNormals.Smooth, randomMass); + break; + + case "bedOfNails": + case "corner": + case "roundedRectangle": + case "triangle": + shape = findShape(shapeName); + addRigidBody( + shape, MeshNormals.Facet, PhysicsBody.massForStatic); + break; + + case "box": + case "frame": + case "halfPipe": + case "hull": + case "iBeam": + case "lidlessBox": + case "platonic": + case "prism": + case "pyramid": + case "star": + case "tetrahedron": + case "triangularFrame": + case "trident": + case "washer": + shape = random.nextShape(shapeName); + addRigidBody(shape, MeshNormals.Facet, randomMass); + break; + + case "candyDish": + case "dimples": + case "smooth": + shape = findShape(shapeName); + addRigidBody( + shape, MeshNormals.Smooth, PhysicsBody.massForStatic); + break; + + case "capsule": + case "cone": + case "coneBox": + case "cylinder": + case "cylinderBox": + case "dome": + case "football": + case "multiSphere": + case "roundedDisc": + case "saucer": + case "snowman": + case "torus": + shape = random.nextShape(shapeName); + addRigidBody(shape, MeshNormals.Smooth, randomMass); + break; + + case "digit": + shape = randomDigit(); + shape.setScale(0.5f); + addRigidBody(shape, MeshNormals.Facet, randomMass); + break; + + case "letter": + shape = randomLetter(); + shape.setScale(0.5f); + addRigidBody(shape, MeshNormals.Facet, randomMass); + break; + + case "mallet": + shape = randomMallet(); + addRigidBody(shape, MeshNormals.Smooth, randomMass); + break; + + case "sphere": + shape = random.nextShape(shapeName); + addRigidBody(shape, MeshNormals.Sphere, randomMass); + break; + + default: + String message = "shapeName = " + MyString.quote(shapeName); + throw new RuntimeException(message); + } + } + + /** + * Split all rigid bodies in the PhysicsSpace. + */ + private void splitAll() { + Vector3f world3 = cam.getLocation(); // alias + Triangle triangle = new Triangle(world1, world2, world3); + + PhysicsSpace space = getPhysicsSpace(); + Collection allPcos = space.getPcoList(); + for (PhysicsCollisionObject pco : allPcos) { + PhysicsRigidBody body = (PhysicsRigidBody) pco; + splitBody(body, triangle); + } + } + + /** + * Attempt to split the specified rigid body using the plane of the + * specified triangle. + * + * @param oldBody (not null, added to the PhysicsSpace) + * @param worldTriangle a triangle that defines the splitting plane (in + * world coordinates, not null, unaffected) + */ + private void splitBody(PhysicsRigidBody oldBody, Triangle worldTriangle) { + CollisionShape originalShape = oldBody.getCollisionShape(); + CollisionShape splittableShape = originalShape.toSplittableShape(); + assert splittableShape.canSplit(); + if (splittableShape instanceof EmptyShape) { + return; // Splitting an empty shape has no effect. + } + + // Transform the triangle to the shape coordinate system. + Transform shapeToWorld = oldBody.getTransform(null); + if (splittableShape instanceof CompoundCollisionShape) { + shapeToWorld.setScale(1f); + } else { + splittableShape.getScale(shapeToWorld.getScale()); + } + Triangle shapeTriangle + = MyMath.transformInverse(shapeToWorld, worldTriangle, null); + + CollisionShape[] shapes; + float[] volumes; + int[] signs; + Vector3f[] locations; + Vector3f worldNormal = worldTriangle.getNormal(); // alias + + if (splittableShape instanceof HullCollisionShape) { + HullCollisionShape hullShape = (HullCollisionShape) splittableShape; + ChildCollisionShape[] children = hullShape.split(shapeTriangle); + assert children.length == 2 : children.length; + if (children[0] == null || children[1] == null) { + return; // The split plane didn't intersect the hull. + } + + shapes = new CollisionShape[2]; + volumes = new float[2]; + signs = new int[2]; + locations = new Vector3f[2]; + for (int sideI = 0; sideI < 2; ++sideI) { + shapes[sideI] = children[sideI].getShape(); + volumes[sideI] = shapes[sideI].scaledVolume(); + signs[sideI] = 2 * sideI - 1; + + Vector3f location = children[sideI].copyOffset(null); + MyMath.transform(shapeToWorld, location, location); + locations[sideI] = location; + } + + } else if (splittableShape instanceof CompoundCollisionShape) { + CompoundCollisionShape compound + = (CompoundCollisionShape) splittableShape; + CompoundCollisionShape[] compounds = compound.split(shapeTriangle); + assert compounds.length == 2 : compounds.length; + if (compounds[0] == null || compounds[1] == null) { + return; // The split plane didn't intersect the compound shape. + } + /* + * Enumerate the groups of connected children + * on each side of the split plane. + */ + List groupList = new ArrayList<>(4); + List signList = new ArrayList<>(4); + CollisionSpace testSpace = getPhysicsSpace(); + for (int sideI = 0; sideI < 2; ++sideI) { + ChildCollisionShape[] children + = compounds[sideI].listChildren(); + int numChildren = children.length; + int[] map = new int[numChildren]; + int numGroups = compounds[sideI].countGroups(testSpace, map); + int sign = 2 * sideI - 1; + + for (int groupI = 0; groupI < numGroups; ++groupI) { + CompoundCollisionShape newGroup + = new CompoundCollisionShape(numChildren); + for (int childI = 0; childI < numChildren; ++childI) { + if (map[childI] == groupI) { + ChildCollisionShape child = children[childI]; + CollisionShape baseShape = child.getShape(); + child.copyOffset(tmpLocation); + child.copyRotationMatrix(tmpMatrix); + newGroup.addChildShape( + baseShape, tmpLocation, tmpMatrix); + } + } + groupList.add(newGroup); + signList.add(sign); + } + } + + int numGroups = groupList.size(); + assert signList.size() == numGroups; + shapes = new CollisionShape[numGroups]; + volumes = new float[numGroups]; + signs = new int[numGroups]; + locations = new Vector3f[numGroups]; + + for (int groupI = 0; groupI < numGroups; ++groupI) { + CompoundCollisionShape shape = groupList.get(groupI); + shapes[groupI] = shape; + volumes[groupI] = shape.scaledVolume(); + signs[groupI] = signList.get(groupI); + /* + * Translate each compound so its AABB is centered at (0,0,0) + * in its shape coordinates. + */ + Vector3f location = shape.aabbCenter(null); + Vector3f offset = location.negate(); + shape.translate(offset); + shapeToWorld.setScale(1f); + MyMath.transform(shapeToWorld, location, location); + locations[groupI] = location; + } + + } else if (splittableShape instanceof GImpactCollisionShape) { + GImpactCollisionShape gi = (GImpactCollisionShape) splittableShape; + ChildCollisionShape[] children = gi.split(shapeTriangle); + assert children.length == 2 : children.length; + if (children[0] == null || children[1] == null) { + return; // The split plane didn't intersect the GImpact shape. + } + + shapes = new CollisionShape[2]; + volumes = new float[2]; + signs = new int[2]; + locations = new Vector3f[2]; + for (int sideI = 0; sideI < 2; ++sideI) { + shapes[sideI] = children[sideI].getShape(); + volumes[sideI] = 1f; // TODO calculate area + signs[sideI] = 2 * sideI - 1; + + Vector3f location = children[sideI].copyOffset(null); + MyMath.transform(shapeToWorld, location, location); + locations[sideI] = location; + } + + } else if (splittableShape instanceof MeshCollisionShape) { + assert oldBody.isStatic(); + MeshCollisionShape mesh = (MeshCollisionShape) splittableShape; + shapes = mesh.split(shapeTriangle); + assert shapes.length == 2 : shapes.length; + if (shapes[0] == null || shapes[1] == null) { + return; // The split plane didn't intersect the mesh shape. + } + + volumes = new float[2]; + signs = new int[2]; + locations = new Vector3f[2]; + for (int sideI = 0; sideI < 2; ++sideI) { + volumes[sideI] = 0f; // unused + signs[sideI] = 2 * sideI - 1; + locations[sideI] = oldBody.getPhysicsLocation(null); + } + + } else { // TODO handle simplex n<=2 + logger.log(Level.WARNING, "Shape not split: {0}", originalShape); + return; + } + + splitBody(oldBody, worldNormal, shapes, volumes, signs, locations); + } + + /** + * Split the specified rigid body using the specified shapes. + * + * @param oldBody (not null, added to the PhysicsSpace) + * @param worldNormal the normal of the splitting plane (in world + * coordinates, not null, unaffected) + * @param shapes the shapes to use (length≥2, all not null) + * @param sizes the estimated relative size of each shape (all >0) + * @param signs -1 → shape is on the negative side of the splitting + * plane, +1 → on the positive side + * @param locations the center location of each shape (in physics-space + * coordinates, all not null) + */ + private void splitBody(PhysicsRigidBody oldBody, Vector3f worldNormal, + CollisionShape[] shapes, float[] sizes, int[] signs, + Vector3f[] locations) { + int numShapes = shapes.length; + assert numShapes >= 2 : numShapes; + assert sizes.length == numShapes : sizes.length; + assert signs.length == numShapes : signs.length; + assert locations.length == numShapes : locations.length; + + // Tweak the locations to create some separation. + boolean isDynamic = oldBody.isDynamic(); + float deltaX = isDynamic ? 0.04f : 0.1f; + for (int shapeI = 0; shapeI < numShapes; ++shapeI) { + float factor = signs[shapeI] * deltaX; + MyVector3f.accumulateScaled(locations[shapeI], worldNormal, factor); + } + + float[] masses = new float[numShapes]; + Vector3f v; + Vector3f w; + Vector3f[] velocities = new Vector3f[numShapes]; + if (isDynamic) { + // Tweak the linear velocities to enhance the separation. + float deltaV = 0.04f; + v = oldBody.getLinearVelocity(null); + for (int shapeI = 0; shapeI < numShapes; ++shapeI) { + velocities[shapeI] = v.clone(); + float multiplier = signs[shapeI] * deltaV; + MyVector3f.accumulateScaled( + velocities[shapeI], worldNormal, multiplier); + } + + // Calculate the masses. + float totalSize = 0f; + for (int shapeI = 0; shapeI < numShapes; ++shapeI) { + totalSize += sizes[shapeI]; + } + assert totalSize > 0f : totalSize; + float totalMass = oldBody.getMass(); + for (int shapeI = 0; shapeI < numShapes; ++shapeI) { + masses[shapeI] = totalMass * sizes[shapeI] / totalSize; + assert masses[shapeI] > 0f : masses[shapeI]; + } + + w = oldBody.getAngularVelocity(null); + + } else { + for (int shapeI = 0; shapeI < numShapes; ++shapeI) { + masses[shapeI] = PhysicsBody.massForStatic; + } + w = null; + } + + MeshNormals debugMeshNormals = oldBody.debugMeshNormals(); + Quaternion orientation = oldBody.getPhysicsRotation(null); + + PhysicsSpace space = getPhysicsSpace(); + space.removeCollisionObject(oldBody); + + for (int shapeI = 0; shapeI < numShapes; ++shapeI) { + PhysicsRigidBody body + = new PhysicsRigidBody(shapes[shapeI], masses[shapeI]); + body.setDebugMeshNormals(debugMeshNormals); + body.setPhysicsLocation(locations[shapeI]); + body.setPhysicsRotation(orientation); + if (isDynamic) { + body.setAngularVelocity(w); + body.setLinearVelocity(velocities[shapeI]); + } + addCollisionObject(body); + } + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemoStatus.java b/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemoStatus.java index 036cc8df6..2524c2272 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemoStatus.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemoStatus.java @@ -1,358 +1,358 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.math.ColorRGBA; -import java.util.Arrays; -import java.util.logging.Logger; -import jme3utilities.SimpleAppState; -import jme3utilities.math.MyArray; -import jme3utilities.math.MyMath; -import jme3utilities.ui.AcorusDemo; - -/** - * AppState to display the status of the SplitDemo application in an overlay. - * The overlay consists of status lines, one of which is selected for editing. - * The overlay is located in the upper-left portion of the display. - * - * @author Stephen Gold sgold@sonic.net - */ -class SplitDemoStatus extends SimpleAppState { - // ************************************************************************* - // constants and loggers - - /** - * list of collision margins, in ascending order - */ - final private static float[] marginValues = {0.008f, 0.04f, 0.2f, 1f}; - /** - * index of the status line for the collision margin - */ - final private static int marginStatusLine = 3; - /** - * number of lines of text in the overlay - */ - final private static int numStatusLines = 4; - /** - * index of the status line for the type of the next shape - */ - final private static int shapeStatusLine = 2; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(SplitDemoStatus.class.getName()); - /** - * names of all shape types, in ascending lexicographic order - *

- * "bowlingPin" is omitted because its hull shapes are too complex for - * real-time splitting. - *

- * "sieve" is omitted because plane shapes are not splittable. - */ - final private static String[] shapeNames = { - "ankh", "banana", "barbell", "barrel", "bedOfNails", "bowl", "box", - "candyDish", "capsule", "chair", "cone", "coneBox", "corner", - "cylinder", "cylinderBox", "digit", "dimples", "dome", "duck", - "football", "frame", "halfPipe", "heart", "horseshoe", "hull", "iBeam", - "knucklebone", "ladder", "letter", "lidlessBox", "link", "mallet", - "multiSphere", "platonic", "prism", "pyramid", "roundedDisc", - "roundedRectangle", "saucer", "smooth", "snowman", "sphere", "star", - "sword", "table", "teapot", "teapotGi", "tetrahedron", "thumbTack", - "top", "torus", "tray", "triangle", "triangularFrame", "trident", - "washer" - }; - // ************************************************************************* - // fields - - /** - * lines of text displayed in the upper-left corner of the GUI node ([0] is - * the top line) - */ - final private BitmapText[] statusLines = new BitmapText[numStatusLines]; - /** - * flag to enable child coloring for PCOs with compound shapes - */ - private boolean isChildColoring = false; - /** - * flag to force wireframe materials for all PCOs (overrides child coloring) - */ - private boolean isWireframe = false; - /** - * reference to the application instance - */ - private SplitDemo appInstance; - /** - * index of the line being edited (≥1) - */ - private int selectedLine = shapeStatusLine; - /** - * name of the type selected for the next shape - */ - private String nextShapeType = "prism"; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized enabled state. - */ - SplitDemoStatus() { - super(true); - } - // ************************************************************************* - // new methods exposed - - /** - * Advance the field selection by the specified amount. - * - * @param amount the number of fields to move downward - */ - void advanceSelectedField(int amount) { - int firstField = 2; - int numFields = numStatusLines - firstField; - - int selectedField = selectedLine - firstField; - int sum = selectedField + amount; - selectedField = MyMath.modulo(sum, numFields); - this.selectedLine = selectedField + firstField; - } - - /** - * Advance the value of the selected field by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - void advanceValue(int amount) { - switch (selectedLine) { - case marginStatusLine: - advanceMargin(amount); - break; - case shapeStatusLine: - advanceShape(amount); - break; - default: - throw new IllegalStateException("line = " + selectedLine); - } - } - - /** - * Test whether child coloring is enabled. - * - * @return true if enabled, otherwise false - */ - boolean isChildColoring() { - return isChildColoring; - } - - /** - * Test whether wireframe materials are enabled. - * - * @return true if enabled, otherwise false - */ - boolean isWireframe() { - return isWireframe; - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - void resize(int newWidth, int newHeight) { - if (isInitialized()) { - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - } - } - - /** - * Return the name of the selected shape type. - * - * @return the shape name (not null, not empty) - */ - String shapeName() { - assert nextShapeType != null; - assert !nextShapeType.isEmpty(); - return nextShapeType; - } - - /** - * Toggle child coloring disabled/enabled. - */ - void toggleChildColor() { - this.isChildColoring = !isChildColoring; - } - - /** - * Toggle wireframe disabled/enabled. - */ - void toggleWireframe() { - this.isWireframe = !isWireframe; - } - // ************************************************************************* - // ActionAppState methods - - /** - * Clean up this AppState during the first update after it gets detached. - * Should be invoked only by a subclass or by the AppStateManager. - */ - @Override - public void cleanup() { - super.cleanup(); - - // Remove the status lines from the guiNode. - for (int i = 0; i < numStatusLines; ++i) { - statusLines[i].removeFromParent(); - } - } - - /** - * Initialize this AppState on the first update after it gets attached. - * - * @param sm application's state manager (not null) - * @param app application which owns this state (not null) - */ - @Override - public void initialize(AppStateManager sm, Application app) { - super.initialize(sm, app); - - this.appInstance = (SplitDemo) app; - BitmapFont guiFont - = assetManager.loadFont("Interface/Fonts/Default.fnt"); - - // Add status lines to the guiNode. - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - float y = cam.getHeight() - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - guiNode.attachChild(statusLines[lineIndex]); - } - - assert MyArray.isSorted(marginValues); - assert MyArray.isSorted(shapeNames); - } - - /** - * Callback to update this AppState prior to rendering. (Invoked once per - * frame while the state is attached and enabled.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - updateStatusText(); - - float margin = CollisionShape.getDefaultMargin(); - int index = 1 + Arrays.binarySearch(marginValues, margin); - int count = marginValues.length; - String message = String.format( - "Margin #%d of %d: %.3f", index, count, margin); - updateStatusLine(marginStatusLine, message); - - index = 1 + Arrays.binarySearch(shapeNames, nextShapeType); - count = shapeNames.length; - message = String.format( - "Shape #%d of %d: %s", index, count, nextShapeType); - updateStatusLine(shapeStatusLine, message); - } - // ************************************************************************* - // private methods - - /** - * Advance the collision-margin selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private static void advanceMargin(int amount) { - float margin = CollisionShape.getDefaultMargin(); - margin = AcorusDemo.advanceFloat(marginValues, margin, amount); - CollisionShape.setDefaultMargin(margin); - } - - /** - * Advance the next-shape selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceShape(int amount) { - this.nextShapeType - = AcorusDemo.advanceString(shapeNames, nextShapeType, amount); - } - - /** - * Update the indexed status line. - * - * @param lineIndex which status line (≥0) - * @param text the text to display, not including the arrow, if any - */ - private void updateStatusLine(int lineIndex, String text) { - BitmapText spatial = statusLines[lineIndex]; - - if (lineIndex == selectedLine) { - spatial.setColor(ColorRGBA.Yellow); - spatial.setText("-> " + text); - } else { - spatial.setColor(ColorRGBA.White); - spatial.setText(" " + text); - } - } - - /** - * Update the status text (top 2 lines). - */ - private void updateStatusText() { - String message = " View: "; - if (isWireframe) { - message += "Wireframe "; - } else if (isChildColoring) { - message += "Lit/ChildColored "; - } else { - message += "Lit "; - } - String viewOptions = appInstance.describePhysicsDebugOptions(); - message += viewOptions; - statusLines[0].setText(message); - - int numActiveBodies = appInstance.countActive(); - float splitRadians = SplitDemo.splitAngle(); - float splitDegrees = MyMath.toDegrees(splitRadians); - boolean isPaused = appInstance.isPaused(); - message = String.format(" activeBodies=%d splitAngle=%.0f deg%s", - numActiveBodies, splitDegrees, isPaused ? " PAUSED" : ""); - statusLines[1].setText(message); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.math.ColorRGBA; +import java.util.Arrays; +import java.util.logging.Logger; +import jme3utilities.SimpleAppState; +import jme3utilities.math.MyArray; +import jme3utilities.math.MyMath; +import jme3utilities.ui.AcorusDemo; + +/** + * AppState to display the status of the SplitDemo application in an overlay. + * The overlay consists of status lines, one of which is selected for editing. + * The overlay is located in the upper-left portion of the display. + * + * @author Stephen Gold sgold@sonic.net + */ +class SplitDemoStatus extends SimpleAppState { + // ************************************************************************* + // constants and loggers + + /** + * list of collision margins, in ascending order + */ + final private static float[] marginValues = {0.008f, 0.04f, 0.2f, 1f}; + /** + * index of the status line for the collision margin + */ + final private static int marginStatusLine = 3; + /** + * number of lines of text in the overlay + */ + final private static int numStatusLines = 4; + /** + * index of the status line for the type of the next shape + */ + final private static int shapeStatusLine = 2; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(SplitDemoStatus.class.getName()); + /** + * names of all shape types, in ascending lexicographic order + *

+ * "bowlingPin" is omitted because its hull shapes are too complex for + * real-time splitting. + *

+ * "sieve" is omitted because plane shapes are not splittable. + */ + final private static String[] shapeNames = { + "ankh", "banana", "barbell", "barrel", "bedOfNails", "bowl", "box", + "candyDish", "capsule", "chair", "cone", "coneBox", "corner", + "cylinder", "cylinderBox", "digit", "dimples", "dome", "duck", + "football", "frame", "halfPipe", "heart", "horseshoe", "hull", "iBeam", + "knucklebone", "ladder", "letter", "lidlessBox", "link", "mallet", + "multiSphere", "platonic", "prism", "pyramid", "roundedDisc", + "roundedRectangle", "saucer", "smooth", "snowman", "sphere", "star", + "sword", "table", "teapot", "teapotGi", "tetrahedron", "thumbTack", + "top", "torus", "tray", "triangle", "triangularFrame", "trident", + "washer" + }; + // ************************************************************************* + // fields + + /** + * lines of text displayed in the upper-left corner of the GUI node ([0] is + * the top line) + */ + final private BitmapText[] statusLines = new BitmapText[numStatusLines]; + /** + * flag to enable child coloring for PCOs with compound shapes + */ + private boolean isChildColoring = false; + /** + * flag to force wireframe materials for all PCOs (overrides child coloring) + */ + private boolean isWireframe = false; + /** + * reference to the application instance + */ + private SplitDemo appInstance; + /** + * index of the line being edited (≥1) + */ + private int selectedLine = shapeStatusLine; + /** + * name of the type selected for the next shape + */ + private String nextShapeType = "prism"; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized enabled state. + */ + SplitDemoStatus() { + super(true); + } + // ************************************************************************* + // new methods exposed + + /** + * Advance the field selection by the specified amount. + * + * @param amount the number of fields to move downward + */ + void advanceSelectedField(int amount) { + int firstField = 2; + int numFields = numStatusLines - firstField; + + int selectedField = selectedLine - firstField; + int sum = selectedField + amount; + selectedField = MyMath.modulo(sum, numFields); + this.selectedLine = selectedField + firstField; + } + + /** + * Advance the value of the selected field by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + void advanceValue(int amount) { + switch (selectedLine) { + case marginStatusLine: + advanceMargin(amount); + break; + case shapeStatusLine: + advanceShape(amount); + break; + default: + throw new IllegalStateException("line = " + selectedLine); + } + } + + /** + * Test whether child coloring is enabled. + * + * @return true if enabled, otherwise false + */ + boolean isChildColoring() { + return isChildColoring; + } + + /** + * Test whether wireframe materials are enabled. + * + * @return true if enabled, otherwise false + */ + boolean isWireframe() { + return isWireframe; + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + void resize(int newWidth, int newHeight) { + if (isInitialized()) { + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + } + } + + /** + * Return the name of the selected shape type. + * + * @return the shape name (not null, not empty) + */ + String shapeName() { + assert nextShapeType != null; + assert !nextShapeType.isEmpty(); + return nextShapeType; + } + + /** + * Toggle child coloring disabled/enabled. + */ + void toggleChildColor() { + this.isChildColoring = !isChildColoring; + } + + /** + * Toggle wireframe disabled/enabled. + */ + void toggleWireframe() { + this.isWireframe = !isWireframe; + } + // ************************************************************************* + // ActionAppState methods + + /** + * Clean up this AppState during the first update after it gets detached. + * Should be invoked only by a subclass or by the AppStateManager. + */ + @Override + public void cleanup() { + super.cleanup(); + + // Remove the status lines from the guiNode. + for (int i = 0; i < numStatusLines; ++i) { + statusLines[i].removeFromParent(); + } + } + + /** + * Initialize this AppState on the first update after it gets attached. + * + * @param sm application's state manager (not null) + * @param app application which owns this state (not null) + */ + @Override + public void initialize(AppStateManager sm, Application app) { + super.initialize(sm, app); + + this.appInstance = (SplitDemo) app; + BitmapFont guiFont + = assetManager.loadFont("Interface/Fonts/Default.fnt"); + + // Add status lines to the guiNode. + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + float y = cam.getHeight() - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + guiNode.attachChild(statusLines[lineIndex]); + } + + assert MyArray.isSorted(marginValues); + assert MyArray.isSorted(shapeNames); + } + + /** + * Callback to update this AppState prior to rendering. (Invoked once per + * frame while the state is attached and enabled.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + updateStatusText(); + + float margin = CollisionShape.getDefaultMargin(); + int index = 1 + Arrays.binarySearch(marginValues, margin); + int count = marginValues.length; + String message = String.format( + "Margin #%d of %d: %.3f", index, count, margin); + updateStatusLine(marginStatusLine, message); + + index = 1 + Arrays.binarySearch(shapeNames, nextShapeType); + count = shapeNames.length; + message = String.format( + "Shape #%d of %d: %s", index, count, nextShapeType); + updateStatusLine(shapeStatusLine, message); + } + // ************************************************************************* + // private methods + + /** + * Advance the collision-margin selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private static void advanceMargin(int amount) { + float margin = CollisionShape.getDefaultMargin(); + margin = AcorusDemo.advanceFloat(marginValues, margin, amount); + CollisionShape.setDefaultMargin(margin); + } + + /** + * Advance the next-shape selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceShape(int amount) { + this.nextShapeType + = AcorusDemo.advanceString(shapeNames, nextShapeType, amount); + } + + /** + * Update the indexed status line. + * + * @param lineIndex which status line (≥0) + * @param text the text to display, not including the arrow, if any + */ + private void updateStatusLine(int lineIndex, String text) { + BitmapText spatial = statusLines[lineIndex]; + + if (lineIndex == selectedLine) { + spatial.setColor(ColorRGBA.Yellow); + spatial.setText("-> " + text); + } else { + spatial.setColor(ColorRGBA.White); + spatial.setText(" " + text); + } + } + + /** + * Update the status text (top 2 lines). + */ + private void updateStatusText() { + String message = " View: "; + if (isWireframe) { + message += "Wireframe "; + } else if (isChildColoring) { + message += "Lit/ChildColored "; + } else { + message += "Lit "; + } + String viewOptions = appInstance.describePhysicsDebugOptions(); + message += viewOptions; + statusLines[0].setText(message); + + int numActiveBodies = appInstance.countActive(); + float splitRadians = SplitDemo.splitAngle(); + float splitDegrees = MyMath.toDegrees(splitRadians); + boolean isPaused = appInstance.isPaused(); + message = String.format(" activeBodies=%d splitAngle=%.0f deg%s", + numActiveBodies, splitDegrees, isPaused ? " PAUSED" : ""); + statusLines[1].setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemo.java index 333cd42af..2067f07c2 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemo.java @@ -1,1119 +1,1119 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bounding.BoundingBox; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.util.BufferUtils; -import java.util.Collection; -import java.util.List; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.noise.Generator; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; - -/** - * Test/demonstrate dynamic physics by launching missiles (small/dynamic/rigid - * bodies) at various targets. - *

- * Collision objects are rendered entirely by debug visualization. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TargetDemo - extends PhysicsDemo - implements DebugInitListener { - // ************************************************************************* - // constants and loggers - - /** - * Y coordinate for the top surface of the platform (in physics-space - * coordinates) - */ - final private static float platformTopY = 0f; - /** - * number of colors/materials for targets - */ - final private static int numTargetColors = 4; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TargetDemo.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = TargetDemo.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * selected rigid body, or null if none - */ - private static PhysicsRigidBody selectedBody = null; - /** - * AppState to manage the status overlay - */ - private static TargetDemoStatus status; - // ************************************************************************* - // constructors - - /** - * Instantiate the TargetDemo application. - */ - public TargetDemo() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Count how many rigid bodies are active. - * - * @return the count (≥0) - */ - int countActive() { - int result = 0; - Collection rigidBodies - = getPhysicsSpace().getRigidBodyList(); - for (PhysicsRigidBody rigidBody : rigidBodies) { - if (rigidBody.isActive()) { - ++result; - } - } - - return result; - } - - /** - * Main entry point for the TargetDemo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Enable direct-memory tracking. - BufferUtils.setTrackDirectMemoryEnabled(true); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new TargetDemo(); - application.setSettings(settings); - application.start(); - } - - /** - * Restart the current scenario. - */ - void restartScenario() { - selectBody(null); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.destroy(); - assert physicsSpace.isEmpty(); - - String platformName = status.platformType(); - addPlatform(platformName, platformTopY); - - setUpScenario(); - setDebugMaterialsAll(); - } - - /** - * Update the debug materials of all collision objects. - */ - void setDebugMaterialsAll() { - PhysicsSpace physicsSpace = getPhysicsSpace(); - for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { - setDebugMaterial(pco); - } - } - - /** - * Update the ShadowMode of the debug scene. - */ - static void setDebugShadowMode() { - RenderQueue.ShadowMode mode; - if (status.isWireframe()) { - mode = RenderQueue.ShadowMode.Off; - } else { - mode = RenderQueue.ShadowMode.CastAndReceive; - } - bulletAppState.setDebugShadowMode(mode); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - status = new TargetDemoStatus(); - boolean success = stateManager.attach(status); - assert success; - - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - generateShapes(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - String platformName = status.platformType(); - addPlatform(platformName, platformTopY); - - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - renderer.setDefaultAnisotropicFilter(degree); - - setUpScenario(); - } - - /** - * Calculate screen bounds for the detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes below the status. - float margin = 10f; // in pixels - float leftX = margin; - float topY = viewPortHeight - 220f - margin; - float width = viewPortWidth - leftX - margin; - float height = topY - margin; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - ColorRGBA red = new ColorRGBA(0.5f, 0f, 0f, 1f); - Material missile = MyAsset.createShinyMaterial(assetManager, red); - missile.setFloat("Shininess", 15f); - registerMaterial("missile", missile); - - ColorRGBA lightGray = new ColorRGBA(0.6f, 0.6f, 0.6f, 1f); - Material selected - = MyAsset.createShinyMaterial(assetManager, lightGray); - selected.setFloat("Shininess", 15f); - registerMaterial("selected", selected); - - // shiny, lit materials for targets - ColorRGBA[] targetColors = new ColorRGBA[numTargetColors]; - targetColors[0] = new ColorRGBA(0.2f, 0f, 0f, 1f); // ruby - targetColors[1] = new ColorRGBA(0f, 0.07f, 0f, 1f); // emerald - targetColors[2] = new ColorRGBA(0f, 0f, 0.3f, 1f); // sapphire - targetColors[3] = new ColorRGBA(0.2f, 0.1f, 0f, 1f); // topaz - - for (int index = 0; index < targetColors.length; ++index) { - ColorRGBA color = targetColors[index]; - Material material - = MyAsset.createShinyMaterial(assetManager, color); - material.setFloat("Shininess", 15f); - - registerMaterial("target" + index, material); - } - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - dim.bind("delete selected", KeyInput.KEY_DECIMAL, KeyInput.KEY_DELETE); - - dim.bind("dump selected", KeyInput.KEY_LBRACKET); - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind("launch", KeyInput.KEY_RETURN, KeyInput.KEY_INSERT, - KeyInput.KEY_NUMPAD0); - - dim.bind("next field", KeyInput.KEY_NUMPAD2); - dim.bind("next value", KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); - - dim.bind("pick", "RMB"); - dim.bind("pick", KeyInput.KEY_R); - - dim.bind("pop selected", KeyInput.KEY_PGUP); - - dim.bind("previous field", KeyInput.KEY_NUMPAD8); - dim.bind("previous value", KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); - - dim.bind("restart", KeyInput.KEY_NUMPAD5); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleCcdSpheres, KeyInput.KEY_L); - dim.bind("toggle childColor", KeyInput.KEY_COMMA); - dim.bind(asToggleGArrows, KeyInput.KEY_J); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - dim.bind(asToggleWArrows, KeyInput.KEY_N); - dim.bind("toggle wireframe", KeyInput.KEY_SLASH); - } - - /** - * Process an action that wasn't handled by the active InputMode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "delete selected": - deleteSelected(); - return; - case "dump selected": - dumpSelected(); - return; - case "launch": - launchMissile(); - return; - - case "next field": - status.advanceSelectedField(+1); - return; - case "next value": - status.advanceValue(+1); - return; - - case "pick": - pick(); - return; - case "pop selected": - popSelected(); - return; - - case "previous field": - status.advanceSelectedField(-1); - return; - case "previous value": - status.advanceValue(-1); - return; - - case "restart": - restartScenario(); - return; - - case "toggle childColor": - status.toggleChildColor(); - return; - case "toggle wireframe": - status.toggleWireframe(); - return; - - default: - } - } - - // The action is not handled: forward it to the superclass. - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - status.resize(newWidth, newHeight); - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked after adding a collision object to the PhysicsSpace. - * - * @param pco the object that was added (not null) - */ - @Override - public void postAdd(PhysicsCollisionObject pco) { - if (pco instanceof PhysicsRigidBody) { - PhysicsRigidBody rigidBody = (PhysicsRigidBody) pco; - - float damping = status.damping(); - rigidBody.setDamping(damping, damping); - - float linearThreshold = 1f; - float angularThreshold = 1f; - rigidBody.setSleepingThresholds(linearThreshold, angularThreshold); - } - - float friction = status.friction(); - pco.setFriction(friction); - - float restitution = status.restitution(); - pco.setRestitution(restitution); - - setDebugMaterial(pco); - pco.setDebugMeshResolution(DebugShapeFactory.highResolution); - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); - Vector3f direction = new Vector3f(1f, -3f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - rootSpatial.addLight(sun); - sun.setName("sun"); - - viewPort.clearProcessors(); - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.7f); - viewPort.addProcessor(dlsr); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.1f; - float far = 500f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(10f); - flyCam.setZoomSpeed(10f); - - cam.setLocation(new Vector3f(0f, platformTopY + 20f, 40f)); - cam.setRotation(new Quaternion(-0.002f, 0.991408f, -0.1295f, 0.0184f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - DebugShapeFactory.setIndexBuffers(200); - - bulletAppState = new SoftPhysicsAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugInitListener(this); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - stateManager.attach(bulletAppState); - - float gravity = status.gravity(); - setGravityAll(gravity); - } - - /** - * Delete the selected rigid body, if any. - */ - private void deleteSelected() { - if (selectedBody != null) { - getPhysicsSpace().removeCollisionObject(selectedBody); - selectBody(null); - activateAll(); - } - } - - /** - * Dump the selected rigid body, if any. - */ - private void dumpSelected() { - if (selectedBody == null) { - System.out.printf("%nNo body selected."); - } else { - getDumper().dump(selectedBody, ""); - } - } - - /** - * Launch a new missile, its starting position and velocity determined by - * the camera and mouse cursor. - */ - private void launchMissile() { - Vector2f screenXY = inputManager.getCursorPosition(); - Vector3f nearLocation - = cam.getWorldCoordinates(screenXY, MyCamera.nearZ); - Vector3f farLocation = cam.getWorldCoordinates(screenXY, MyCamera.farZ); - - Vector3f direction - = farLocation.subtract(nearLocation).normalizeLocal(); - float initialSpeed = status.missileInitialSpeed(); // psu per second - Vector3f initialVelocity = direction.mult(initialSpeed); - - float radius = status.missileRadius(); // psu - CollisionShape shape = new MultiSphere(radius); - float mass = status.missileMass(); // pmu - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - - Material debugMaterial = findMaterial("missile"); - body.setApplicationData(debugMaterial); - body.setCcdMotionThreshold(radius); - body.setCcdSweptSphereRadius(radius); - body.setDebugMaterial(debugMaterial); - body.setDebugMeshNormals(MeshNormals.Sphere); - body.setDebugMeshResolution(DebugShapeFactory.highResolution); - body.setLinearVelocity(initialVelocity); - body.setPhysicsLocation(nearLocation); - - addCollisionObject(body); - } - - /** - * Cast a physics ray from the cursor and select the nearest rigid body in - * the result. - */ - private void pick() { - List hits = rayTestCursor(); - for (PhysicsRayTestResult hit : hits) { - PhysicsCollisionObject pco = hit.getCollisionObject(); - if (pco instanceof PhysicsRigidBody) { - selectBody((PhysicsRigidBody) pco); - return; - } - } - selectBody(null); - } - - /** - * Apply an upward impulse to the selected rigid body. - */ - private void popSelected() { - if (selectedBody instanceof PhysicsRigidBody) { - float gravity = status.gravity(); - float deltaV = FastMath.sqrt(30f * gravity); - float impulse = selectedBody.getMass() * deltaV; - Vector3f impulseVector = new Vector3f(0f, impulse, 0f); - Generator random = getGenerator(); - Vector3f offset = random.nextVector3f().multLocal(0.2f); - PhysicsRigidBody rigidBody = selectedBody; - rigidBody.applyImpulse(impulseVector, offset); - } - } - - /** - * Register a spherical shape with the specified radius. - * - * @param radius the desired radius (in physics-space units, >0) - */ - private void registerBallShape(float radius) { - unregisterShape("ball"); - CollisionShape shape = new SphereCollisionShape(radius); - registerShape("ball", shape); - } - - /** - * Register a bowling-pin shape with the specified radius. - * - * @param radius the desired radius (in physics-space units, >0) - */ - private void registerBowlingPinShape(float radius) { - unregisterShape("bowlingPin"); - - String bowlingPinPath = "CollisionShapes/bowlingPin.j3o"; - CollisionShape shape - = (CollisionShape) assetManager.loadAsset(bowlingPinPath); - shape = Heart.deepCopy(shape); - - BoundingBox bounds - = shape.boundingBox(Vector3f.ZERO, Matrix3f.IDENTITY, null); - float xHalfExtent = bounds.getXExtent(); - float yHalfExtent = bounds.getYExtent(); - float unscaledRadius = (xHalfExtent + yHalfExtent) / 2f; - float scale = radius / unscaledRadius; - shape.setScale(scale); - - registerShape("bowlingPin", shape); - } - - /** - * Register a brick shape with the specified name, height, and length. - * - * @param shapeName (Z axis, not null) - * @param height the total height (Y axis, in physics-space units, >0) - * @param length the total length (X axis, in physics-space units, >0) - * @param depth the total depth (Z axis, in physics-space units, >0) - */ - private void registerBrickShape( - String shapeName, float height, float length, float depth) { - unregisterShape(shapeName); - - float halfHeight = height / 2f; - float halfLength = length / 2f; - float halfDepth = depth / 2f; - CollisionShape shape - = new BoxCollisionShape(halfLength, halfHeight, halfDepth); - - registerShape(shapeName, shape); - } - - /** - * Register a can shape with the specified radius and height. - * - * @param radius the radius (in physics-space units, >0) - * @param height the total height (Y axis, in physics-space units, >0) - */ - private void registerCanShape(float radius, float height) { - unregisterShape("can"); - CollisionShape shape = new CylinderCollisionShape( - radius, height, PhysicsSpace.AXIS_Y); - registerShape("can", shape); - } - - /** - * Register a domino shape with the specified length. - * - * @param length the total length (Y axis, in physics-space units, >0) - */ - private void registerDominoShape(float length) { - unregisterShape("domino"); - - float halfLength = length / 2f; - float halfThickness = halfLength / 5f; - float halfWidth = halfLength / 2f; - CollisionShape shape - = new BoxCollisionShape(halfThickness, halfLength, halfWidth); - - registerShape("domino", shape); - } - - /** - * Alter which rigid body is selected. - * - * @param rigidBody the body to select (alias created) or null for none - */ - private void selectBody(PhysicsRigidBody rigidBody) { - if (rigidBody != selectedBody) { - selectedBody = rigidBody; - setDebugMaterialsAll(); - } - } - - /** - * Update the debug materials of the specified collision object. - * - * @param pco the collision object to modify (not null) - */ - private void setDebugMaterial(PhysicsCollisionObject pco) { - CollisionShape shape = pco.getCollisionShape(); - - Material debugMaterial; - if (selectedBody == pco) { - debugMaterial = findMaterial("selected"); - - } else if (status.isWireframe()) { - debugMaterial = null; - - } else if (status.isChildColoring() - && shape instanceof CompoundCollisionShape) { - debugMaterial = BulletDebugAppState.enableChildColoring; - - } else { - // Use the previously set lit material. - debugMaterial = (Material) pco.getApplicationData(); - } - - pco.setDebugMaterial(debugMaterial); - } - - /** - * Set up a single ball as a target. - * - * @param location the desired location (in physics-space coordinates, not - * null, unaffected) - */ - private void setUpBall(Vector3f location) { - CollisionShape shape = findShape("ball"); - float mass = 0.2f; - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setDebugMeshNormals(MeshNormals.Sphere); - body.setPhysicsLocation(location); - - setUpTarget(body); - } - - /** - * Set up a single bowling pin as a target. - * - * @param location the desired location (in physics-space coordinates, not - * null, unaffected) - */ - private void setUpBowlingPin(Vector3f location) { - CollisionShape shape = findShape("bowlingPin"); - float mass = 0.2f; - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setDebugMeshNormals(MeshNormals.Smooth); - body.setPhysicsLocation(location); - - Quaternion rotation - = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); - body.setPhysicsRotation(rotation); - - setUpTarget(body); - } - - /** - * Set up a single brick as a target. - * - * @param shapeName the key to the shapes library - * @param location the desired world location (not null, unaffected) - * @param orientation the desired world orientation (not null, unaffected) - */ - private void setUpBrick( - String shapeName, Vector3f location, Quaternion orientation) { - CollisionShape shape = findShape(shapeName); - - float mass = 3f; - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setDebugMeshNormals(MeshNormals.Facet); - body.setPhysicsLocation(location); - body.setPhysicsRotation(orientation); - - setUpTarget(body); - } - - /** - * Set up a round tower of bricks. - * - * @param numRings the desired number of rings/layers of bricks (≥0) - * @param numBricksPerRing the desired number of bricks per ring (>0) - * @param thickness the thickness of the tower wall (>0) - */ - private void setUpBrickTower( - int numRings, int numBricksPerRing, float thickness) { - float innerDiameter = 32f - 2f * thickness; - float innerCircumference = FastMath.PI * innerDiameter; - float insideSpacing = innerCircumference / numBricksPerRing; - float insideGap = 0.05f * insideSpacing; - float length = insideSpacing - insideGap; - float height = Math.min(length, thickness) / MyMath.phi; - registerBrickShape("tower", height, length, thickness); - - float angleStep = FastMath.TWO_PI / numBricksPerRing; - float midRadius = (innerDiameter + thickness) / 2f; - float y0 = platformTopY + height / 2f; - Quaternion orientation = new Quaternion(); - Vector3f location = new Vector3f(0f, y0, midRadius); - for (int ringIndex = 0; ringIndex < numRings; ++ringIndex) { - float theta0; - if (MyMath.isOdd(ringIndex)) { - theta0 = angleStep / 2f; - } else { - theta0 = 0f; - } - for (int j = 0; j < numBricksPerRing; ++j) { - float theta = theta0 + j * angleStep; - location.x = midRadius * FastMath.sin(theta); - location.z = midRadius * FastMath.cos(theta); - orientation.fromAngles(0f, theta, 0f); - setUpBrick("tower", location, orientation); - } - location.y += height; - } - } - - /** - * Erect a brick wall along the X axis. - * - * @param numRows the desired number of rows of bricks (≥0) - * @param numBricksPerRow the desired number of bricks per row (≥1) - */ - private void setUpBrickWall(int numRows, int numBricksPerRow) { - float xSpacing = 32f / numBricksPerRow; // center-to-center - float xGap = 0.1f * xSpacing; - float length = xSpacing - xGap; - float shortLength = (length - xGap) / 2f; - float thickness = length / MyMath.phi; - float height = thickness / MyMath.phi; - registerBrickShape("short", height, shortLength, thickness); - registerBrickShape("long", height, length, thickness); - - float x0even = -xSpacing * (numBricksPerRow - 1) / 2f; - float endSpacing = xGap + (length + shortLength) / 2f; - float x1odd = x0even + xSpacing / 2f - endSpacing; - float y0 = platformTopY + height / 2f; - float ySpacing = 1f * height; // center-to-center - Vector3f location = new Vector3f(x0even, y0, 0f); - for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { - if (MyMath.isOdd(rowIndex)) { - location.x = x1odd; - setUpBrick("short", location, Quaternion.IDENTITY); - location.x += endSpacing; - - for (int j = 1; j < numBricksPerRow; ++j) { - setUpBrick("long", location, Quaternion.IDENTITY); - location.x += xSpacing; - } - - location.x += endSpacing - xSpacing; - setUpBrick("short", location, Quaternion.IDENTITY); - - } else { - location.x = x0even; - for (int j = 0; j < numBricksPerRow; ++j) { - setUpBrick("long", location, Quaternion.IDENTITY); - location.x += xSpacing; - } - } - location.y += ySpacing; - } - } - - /** - * Set up a single can as a target. - * - * @param location the desired location (in physics-space coordinates, not - * null, unaffected) - */ - private void setUpCan(Vector3f location) { - CollisionShape shape = findShape("can"); - float mass = 10f; - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setDebugMeshNormals(MeshNormals.Smooth); - body.setPhysicsLocation(location); - - setUpTarget(body); - } - - /** - * Erect a pyramid of cans along the X axis. - * - * @param numRows the desired number of rows in the pyramid (≥1) - */ - private void setUpCanPyramid(int numRows) { - float xSpacing = 32f / numRows; // center-to-center - float xGap = 0.1f * xSpacing; - float radius = (xSpacing - xGap) / 2f; - float height = 2.65f * radius; - registerCanShape(radius, height); - - float ySpacing = 1f * height; // center-to-center - float y0 = platformTopY + height / 2f; - Vector3f location = new Vector3f(0, y0, 0f); - for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { - int numCansInRow = numRows - rowIndex; - location.x = -(numCansInRow - 1) * xSpacing / 2f; - for (int j = 0; j < numCansInRow; ++j) { - setUpCan(location); - location.x += xSpacing; - } - location.y += ySpacing; - } - } - - /** - * Set up a single domino as a target. - * - * @param location the desired location (in physics-space coordinates, not - * null, unaffected) - * @param orientation the desired orientation (in physics-space coordinates, - * not null, unaffected) - */ - private void setUpDomino(Vector3f location, Quaternion orientation) { - CollisionShape shape = findShape("domino"); - - float mass = 10f; - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setDebugMeshNormals(MeshNormals.Facet); - body.setPhysicsLocation(location); - body.setPhysicsRotation(orientation); - - setUpTarget(body); - } - - /** - * Set up a row of dominoes, evenly-spaced along the X axis. - * - * @param numDominoes the desired number of dominoes (≥1) - */ - private void setUpDominoRow(int numDominoes) { - float xSpacing = 32f / numDominoes; // center-to-center - float length = 1.6f * xSpacing; - registerDominoShape(length); - - float x0 = -xSpacing * (numDominoes - 1) / 2f; - float y = platformTopY + length / 2; - Vector3f location = new Vector3f(x0, y, 0f); - for (int j = 0; j < numDominoes; ++j) { - setUpDomino(location, Quaternion.IDENTITY); - location.x += xSpacing; - } - } - - /** - * Set up 15 balls in a wedge, as if for a game of Pool. - */ - private void setUpPool() { - int numRows = 5; - float xSpacing = 32f / numRows; // center-to-center - float radius = 0.48f * xSpacing; - registerBallShape(radius); - - float zSpacing = xSpacing / FastMath.sqrt(1.5f); // center-to-center - float z0 = (numRows - 1) * zSpacing / 2f; - Vector3f location = new Vector3f(0, radius, z0); - for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { - int numBallsInRow = rowIndex + 1; - location.x = -(numBallsInRow - 1) * xSpacing / 2f; - for (int j = 0; j < numBallsInRow; ++j) { - setUpBall(location); - location.x += xSpacing; - } - location.z -= zSpacing; - } - } - - /** - * Set up all targets for selected scenario. - */ - private void setUpScenario() { - String scenarioName = status.scenarioName(); - switch (scenarioName) { - case "brick tower": { - int numRings = 10; - int numBricksPerRing = 16; - float thickness = 2f; - setUpBrickTower(numRings, numBricksPerRing, thickness); - break; - } - - case "brick wall": { - int numRows = 14; - int numBricksPerRow = 10; - setUpBrickWall(numRows, numBricksPerRow); - break; - } - - case "can pyramid": { - int numRows = 15; - setUpCanPyramid(numRows); - break; - } - - case "domino row": { - int numDominoes = 25; - setUpDominoRow(numDominoes); - break; - } - - case "empty": - break; - - case "pool": - setUpPool(); - break; - - case "tenpin": - setUpTenpin(); - break; - - default: - String msg = "scenarioName = " + MyString.quote(scenarioName); - throw new RuntimeException(msg); - } - } - - /** - * Set up a single target body whose position and debug normals have already - * been configured. - * - * @param body the body to set up (not null, not in world) - */ - private void setUpTarget(PhysicsRigidBody body) { - assert body != null; - assert !body.isInWorld(); - - Random random = getGenerator(); - String materialName = "target" + random.nextInt(numTargetColors); - Material debugMaterial = findMaterial(materialName); - body.setApplicationData(debugMaterial); - - addCollisionObject(body); - } - - /** - * Set up 10 bowling pins in a wedge, as if for a game of (ten-pin) bowling. - */ - private void setUpTenpin() { - int numRows = 4; - float xSpacing = 32f / numRows; // center-to-center - float radius = 0.2f * xSpacing; - registerBowlingPinShape(radius); - - float y0 = 2.50f * radius; - float zSpacing = xSpacing / FastMath.sqrt(1.5f); // center-to-center - float z0 = (numRows - 1) * zSpacing / 2f; - Vector3f location = new Vector3f(0, y0, z0); - for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { - int numPinsInRow = rowIndex + 1; - location.x = -(numPinsInRow - 1) * xSpacing / 2f; - for (int j = 0; j < numPinsInRow; ++j) { - setUpBowlingPin(location); - location.x += xSpacing; - } - location.z -= zSpacing; - } - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.util.BufferUtils; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.noise.Generator; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; + +/** + * Test/demonstrate dynamic physics by launching missiles (small/dynamic/rigid + * bodies) at various targets. + *

+ * Collision objects are rendered entirely by debug visualization. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TargetDemo + extends PhysicsDemo + implements DebugInitListener { + // ************************************************************************* + // constants and loggers + + /** + * Y coordinate for the top surface of the platform (in physics-space + * coordinates) + */ + final private static float platformTopY = 0f; + /** + * number of colors/materials for targets + */ + final private static int numTargetColors = 4; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TargetDemo.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = TargetDemo.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * selected rigid body, or null if none + */ + private static PhysicsRigidBody selectedBody = null; + /** + * AppState to manage the status overlay + */ + private static TargetDemoStatus status; + // ************************************************************************* + // constructors + + /** + * Instantiate the TargetDemo application. + */ + public TargetDemo() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Count how many rigid bodies are active. + * + * @return the count (≥0) + */ + int countActive() { + int result = 0; + Collection rigidBodies + = getPhysicsSpace().getRigidBodyList(); + for (PhysicsRigidBody rigidBody : rigidBodies) { + if (rigidBody.isActive()) { + ++result; + } + } + + return result; + } + + /** + * Main entry point for the TargetDemo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Enable direct-memory tracking. + BufferUtils.setTrackDirectMemoryEnabled(true); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new TargetDemo(); + application.setSettings(settings); + application.start(); + } + + /** + * Restart the current scenario. + */ + void restartScenario() { + selectBody(null); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.destroy(); + assert physicsSpace.isEmpty(); + + String platformName = status.platformType(); + addPlatform(platformName, platformTopY); + + setUpScenario(); + setDebugMaterialsAll(); + } + + /** + * Update the debug materials of all collision objects. + */ + void setDebugMaterialsAll() { + PhysicsSpace physicsSpace = getPhysicsSpace(); + for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { + setDebugMaterial(pco); + } + } + + /** + * Update the ShadowMode of the debug scene. + */ + static void setDebugShadowMode() { + RenderQueue.ShadowMode mode; + if (status.isWireframe()) { + mode = RenderQueue.ShadowMode.Off; + } else { + mode = RenderQueue.ShadowMode.CastAndReceive; + } + bulletAppState.setDebugShadowMode(mode); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + status = new TargetDemoStatus(); + boolean success = stateManager.attach(status); + assert success; + + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + generateShapes(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + String platformName = status.platformType(); + addPlatform(platformName, platformTopY); + + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + renderer.setDefaultAnisotropicFilter(degree); + + setUpScenario(); + } + + /** + * Calculate screen bounds for the detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes below the status. + float margin = 10f; // in pixels + float leftX = margin; + float topY = viewPortHeight - 220f - margin; + float width = viewPortWidth - leftX - margin; + float height = topY - margin; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + ColorRGBA red = new ColorRGBA(0.5f, 0f, 0f, 1f); + Material missile = MyAsset.createShinyMaterial(assetManager, red); + missile.setFloat("Shininess", 15f); + registerMaterial("missile", missile); + + ColorRGBA lightGray = new ColorRGBA(0.6f, 0.6f, 0.6f, 1f); + Material selected + = MyAsset.createShinyMaterial(assetManager, lightGray); + selected.setFloat("Shininess", 15f); + registerMaterial("selected", selected); + + // shiny, lit materials for targets + ColorRGBA[] targetColors = new ColorRGBA[numTargetColors]; + targetColors[0] = new ColorRGBA(0.2f, 0f, 0f, 1f); // ruby + targetColors[1] = new ColorRGBA(0f, 0.07f, 0f, 1f); // emerald + targetColors[2] = new ColorRGBA(0f, 0f, 0.3f, 1f); // sapphire + targetColors[3] = new ColorRGBA(0.2f, 0.1f, 0f, 1f); // topaz + + for (int index = 0; index < targetColors.length; ++index) { + ColorRGBA color = targetColors[index]; + Material material + = MyAsset.createShinyMaterial(assetManager, color); + material.setFloat("Shininess", 15f); + + registerMaterial("target" + index, material); + } + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + dim.bind("delete selected", KeyInput.KEY_DECIMAL, KeyInput.KEY_DELETE); + + dim.bind("dump selected", KeyInput.KEY_LBRACKET); + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind("launch", KeyInput.KEY_RETURN, KeyInput.KEY_INSERT, + KeyInput.KEY_NUMPAD0); + + dim.bind("next field", KeyInput.KEY_NUMPAD2); + dim.bind("next value", KeyInput.KEY_EQUALS, KeyInput.KEY_NUMPAD6); + + dim.bind("pick", "RMB"); + dim.bind("pick", KeyInput.KEY_R); + + dim.bind("pop selected", KeyInput.KEY_PGUP); + + dim.bind("previous field", KeyInput.KEY_NUMPAD8); + dim.bind("previous value", KeyInput.KEY_MINUS, KeyInput.KEY_NUMPAD4); + + dim.bind("restart", KeyInput.KEY_NUMPAD5); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleCcdSpheres, KeyInput.KEY_L); + dim.bind("toggle childColor", KeyInput.KEY_COMMA); + dim.bind(asToggleGArrows, KeyInput.KEY_J); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + dim.bind(asToggleWArrows, KeyInput.KEY_N); + dim.bind("toggle wireframe", KeyInput.KEY_SLASH); + } + + /** + * Process an action that wasn't handled by the active InputMode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "delete selected": + deleteSelected(); + return; + case "dump selected": + dumpSelected(); + return; + case "launch": + launchMissile(); + return; + + case "next field": + status.advanceSelectedField(+1); + return; + case "next value": + status.advanceValue(+1); + return; + + case "pick": + pick(); + return; + case "pop selected": + popSelected(); + return; + + case "previous field": + status.advanceSelectedField(-1); + return; + case "previous value": + status.advanceValue(-1); + return; + + case "restart": + restartScenario(); + return; + + case "toggle childColor": + status.toggleChildColor(); + return; + case "toggle wireframe": + status.toggleWireframe(); + return; + + default: + } + } + + // The action is not handled: forward it to the superclass. + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + status.resize(newWidth, newHeight); + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked after adding a collision object to the PhysicsSpace. + * + * @param pco the object that was added (not null) + */ + @Override + public void postAdd(PhysicsCollisionObject pco) { + if (pco instanceof PhysicsRigidBody) { + PhysicsRigidBody rigidBody = (PhysicsRigidBody) pco; + + float damping = status.damping(); + rigidBody.setDamping(damping, damping); + + float linearThreshold = 1f; + float angularThreshold = 1f; + rigidBody.setSleepingThresholds(linearThreshold, angularThreshold); + } + + float friction = status.friction(); + pco.setFriction(friction); + + float restitution = status.restitution(); + pco.setRestitution(restitution); + + setDebugMaterial(pco); + pco.setDebugMeshResolution(DebugShapeFactory.highResolution); + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); + Vector3f direction = new Vector3f(1f, -3f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + rootSpatial.addLight(sun); + sun.setName("sun"); + + viewPort.clearProcessors(); + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.7f); + viewPort.addProcessor(dlsr); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.1f; + float far = 500f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(10f); + flyCam.setZoomSpeed(10f); + + cam.setLocation(new Vector3f(0f, platformTopY + 20f, 40f)); + cam.setRotation(new Quaternion(-0.002f, 0.991408f, -0.1295f, 0.0184f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + DebugShapeFactory.setIndexBuffers(200); + + bulletAppState = new SoftPhysicsAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugInitListener(this); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + stateManager.attach(bulletAppState); + + float gravity = status.gravity(); + setGravityAll(gravity); + } + + /** + * Delete the selected rigid body, if any. + */ + private void deleteSelected() { + if (selectedBody != null) { + getPhysicsSpace().removeCollisionObject(selectedBody); + selectBody(null); + activateAll(); + } + } + + /** + * Dump the selected rigid body, if any. + */ + private void dumpSelected() { + if (selectedBody == null) { + System.out.printf("%nNo body selected."); + } else { + getDumper().dump(selectedBody, ""); + } + } + + /** + * Launch a new missile, its starting position and velocity determined by + * the camera and mouse cursor. + */ + private void launchMissile() { + Vector2f screenXY = inputManager.getCursorPosition(); + Vector3f nearLocation + = cam.getWorldCoordinates(screenXY, MyCamera.nearZ); + Vector3f farLocation = cam.getWorldCoordinates(screenXY, MyCamera.farZ); + + Vector3f direction + = farLocation.subtract(nearLocation).normalizeLocal(); + float initialSpeed = status.missileInitialSpeed(); // psu per second + Vector3f initialVelocity = direction.mult(initialSpeed); + + float radius = status.missileRadius(); // psu + CollisionShape shape = new MultiSphere(radius); + float mass = status.missileMass(); // pmu + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + + Material debugMaterial = findMaterial("missile"); + body.setApplicationData(debugMaterial); + body.setCcdMotionThreshold(radius); + body.setCcdSweptSphereRadius(radius); + body.setDebugMaterial(debugMaterial); + body.setDebugMeshNormals(MeshNormals.Sphere); + body.setDebugMeshResolution(DebugShapeFactory.highResolution); + body.setLinearVelocity(initialVelocity); + body.setPhysicsLocation(nearLocation); + + addCollisionObject(body); + } + + /** + * Cast a physics ray from the cursor and select the nearest rigid body in + * the result. + */ + private void pick() { + List hits = rayTestCursor(); + for (PhysicsRayTestResult hit : hits) { + PhysicsCollisionObject pco = hit.getCollisionObject(); + if (pco instanceof PhysicsRigidBody) { + selectBody((PhysicsRigidBody) pco); + return; + } + } + selectBody(null); + } + + /** + * Apply an upward impulse to the selected rigid body. + */ + private void popSelected() { + if (selectedBody instanceof PhysicsRigidBody) { + float gravity = status.gravity(); + float deltaV = FastMath.sqrt(30f * gravity); + float impulse = selectedBody.getMass() * deltaV; + Vector3f impulseVector = new Vector3f(0f, impulse, 0f); + Generator random = getGenerator(); + Vector3f offset = random.nextVector3f().multLocal(0.2f); + PhysicsRigidBody rigidBody = selectedBody; + rigidBody.applyImpulse(impulseVector, offset); + } + } + + /** + * Register a spherical shape with the specified radius. + * + * @param radius the desired radius (in physics-space units, >0) + */ + private void registerBallShape(float radius) { + unregisterShape("ball"); + CollisionShape shape = new SphereCollisionShape(radius); + registerShape("ball", shape); + } + + /** + * Register a bowling-pin shape with the specified radius. + * + * @param radius the desired radius (in physics-space units, >0) + */ + private void registerBowlingPinShape(float radius) { + unregisterShape("bowlingPin"); + + String bowlingPinPath = "CollisionShapes/bowlingPin.j3o"; + CollisionShape shape + = (CollisionShape) assetManager.loadAsset(bowlingPinPath); + shape = Heart.deepCopy(shape); + + BoundingBox bounds + = shape.boundingBox(Vector3f.ZERO, Matrix3f.IDENTITY, null); + float xHalfExtent = bounds.getXExtent(); + float yHalfExtent = bounds.getYExtent(); + float unscaledRadius = (xHalfExtent + yHalfExtent) / 2f; + float scale = radius / unscaledRadius; + shape.setScale(scale); + + registerShape("bowlingPin", shape); + } + + /** + * Register a brick shape with the specified name, height, and length. + * + * @param shapeName (Z axis, not null) + * @param height the total height (Y axis, in physics-space units, >0) + * @param length the total length (X axis, in physics-space units, >0) + * @param depth the total depth (Z axis, in physics-space units, >0) + */ + private void registerBrickShape( + String shapeName, float height, float length, float depth) { + unregisterShape(shapeName); + + float halfHeight = height / 2f; + float halfLength = length / 2f; + float halfDepth = depth / 2f; + CollisionShape shape + = new BoxCollisionShape(halfLength, halfHeight, halfDepth); + + registerShape(shapeName, shape); + } + + /** + * Register a can shape with the specified radius and height. + * + * @param radius the radius (in physics-space units, >0) + * @param height the total height (Y axis, in physics-space units, >0) + */ + private void registerCanShape(float radius, float height) { + unregisterShape("can"); + CollisionShape shape = new CylinderCollisionShape( + radius, height, PhysicsSpace.AXIS_Y); + registerShape("can", shape); + } + + /** + * Register a domino shape with the specified length. + * + * @param length the total length (Y axis, in physics-space units, >0) + */ + private void registerDominoShape(float length) { + unregisterShape("domino"); + + float halfLength = length / 2f; + float halfThickness = halfLength / 5f; + float halfWidth = halfLength / 2f; + CollisionShape shape + = new BoxCollisionShape(halfThickness, halfLength, halfWidth); + + registerShape("domino", shape); + } + + /** + * Alter which rigid body is selected. + * + * @param rigidBody the body to select (alias created) or null for none + */ + private void selectBody(PhysicsRigidBody rigidBody) { + if (rigidBody != selectedBody) { + selectedBody = rigidBody; + setDebugMaterialsAll(); + } + } + + /** + * Update the debug materials of the specified collision object. + * + * @param pco the collision object to modify (not null) + */ + private void setDebugMaterial(PhysicsCollisionObject pco) { + CollisionShape shape = pco.getCollisionShape(); + + Material debugMaterial; + if (selectedBody == pco) { + debugMaterial = findMaterial("selected"); + + } else if (status.isWireframe()) { + debugMaterial = null; + + } else if (status.isChildColoring() + && shape instanceof CompoundCollisionShape) { + debugMaterial = BulletDebugAppState.enableChildColoring; + + } else { + // Use the previously set lit material. + debugMaterial = (Material) pco.getApplicationData(); + } + + pco.setDebugMaterial(debugMaterial); + } + + /** + * Set up a single ball as a target. + * + * @param location the desired location (in physics-space coordinates, not + * null, unaffected) + */ + private void setUpBall(Vector3f location) { + CollisionShape shape = findShape("ball"); + float mass = 0.2f; + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setDebugMeshNormals(MeshNormals.Sphere); + body.setPhysicsLocation(location); + + setUpTarget(body); + } + + /** + * Set up a single bowling pin as a target. + * + * @param location the desired location (in physics-space coordinates, not + * null, unaffected) + */ + private void setUpBowlingPin(Vector3f location) { + CollisionShape shape = findShape("bowlingPin"); + float mass = 0.2f; + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setDebugMeshNormals(MeshNormals.Smooth); + body.setPhysicsLocation(location); + + Quaternion rotation + = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); + body.setPhysicsRotation(rotation); + + setUpTarget(body); + } + + /** + * Set up a single brick as a target. + * + * @param shapeName the key to the shapes library + * @param location the desired world location (not null, unaffected) + * @param orientation the desired world orientation (not null, unaffected) + */ + private void setUpBrick( + String shapeName, Vector3f location, Quaternion orientation) { + CollisionShape shape = findShape(shapeName); + + float mass = 3f; + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setDebugMeshNormals(MeshNormals.Facet); + body.setPhysicsLocation(location); + body.setPhysicsRotation(orientation); + + setUpTarget(body); + } + + /** + * Set up a round tower of bricks. + * + * @param numRings the desired number of rings/layers of bricks (≥0) + * @param numBricksPerRing the desired number of bricks per ring (>0) + * @param thickness the thickness of the tower wall (>0) + */ + private void setUpBrickTower( + int numRings, int numBricksPerRing, float thickness) { + float innerDiameter = 32f - 2f * thickness; + float innerCircumference = FastMath.PI * innerDiameter; + float insideSpacing = innerCircumference / numBricksPerRing; + float insideGap = 0.05f * insideSpacing; + float length = insideSpacing - insideGap; + float height = Math.min(length, thickness) / MyMath.phi; + registerBrickShape("tower", height, length, thickness); + + float angleStep = FastMath.TWO_PI / numBricksPerRing; + float midRadius = (innerDiameter + thickness) / 2f; + float y0 = platformTopY + height / 2f; + Quaternion orientation = new Quaternion(); + Vector3f location = new Vector3f(0f, y0, midRadius); + for (int ringIndex = 0; ringIndex < numRings; ++ringIndex) { + float theta0; + if (MyMath.isOdd(ringIndex)) { + theta0 = angleStep / 2f; + } else { + theta0 = 0f; + } + for (int j = 0; j < numBricksPerRing; ++j) { + float theta = theta0 + j * angleStep; + location.x = midRadius * FastMath.sin(theta); + location.z = midRadius * FastMath.cos(theta); + orientation.fromAngles(0f, theta, 0f); + setUpBrick("tower", location, orientation); + } + location.y += height; + } + } + + /** + * Erect a brick wall along the X axis. + * + * @param numRows the desired number of rows of bricks (≥0) + * @param numBricksPerRow the desired number of bricks per row (≥1) + */ + private void setUpBrickWall(int numRows, int numBricksPerRow) { + float xSpacing = 32f / numBricksPerRow; // center-to-center + float xGap = 0.1f * xSpacing; + float length = xSpacing - xGap; + float shortLength = (length - xGap) / 2f; + float thickness = length / MyMath.phi; + float height = thickness / MyMath.phi; + registerBrickShape("short", height, shortLength, thickness); + registerBrickShape("long", height, length, thickness); + + float x0even = -xSpacing * (numBricksPerRow - 1) / 2f; + float endSpacing = xGap + (length + shortLength) / 2f; + float x1odd = x0even + xSpacing / 2f - endSpacing; + float y0 = platformTopY + height / 2f; + float ySpacing = 1f * height; // center-to-center + Vector3f location = new Vector3f(x0even, y0, 0f); + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + if (MyMath.isOdd(rowIndex)) { + location.x = x1odd; + setUpBrick("short", location, Quaternion.IDENTITY); + location.x += endSpacing; + + for (int j = 1; j < numBricksPerRow; ++j) { + setUpBrick("long", location, Quaternion.IDENTITY); + location.x += xSpacing; + } + + location.x += endSpacing - xSpacing; + setUpBrick("short", location, Quaternion.IDENTITY); + + } else { + location.x = x0even; + for (int j = 0; j < numBricksPerRow; ++j) { + setUpBrick("long", location, Quaternion.IDENTITY); + location.x += xSpacing; + } + } + location.y += ySpacing; + } + } + + /** + * Set up a single can as a target. + * + * @param location the desired location (in physics-space coordinates, not + * null, unaffected) + */ + private void setUpCan(Vector3f location) { + CollisionShape shape = findShape("can"); + float mass = 10f; + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setDebugMeshNormals(MeshNormals.Smooth); + body.setPhysicsLocation(location); + + setUpTarget(body); + } + + /** + * Erect a pyramid of cans along the X axis. + * + * @param numRows the desired number of rows in the pyramid (≥1) + */ + private void setUpCanPyramid(int numRows) { + float xSpacing = 32f / numRows; // center-to-center + float xGap = 0.1f * xSpacing; + float radius = (xSpacing - xGap) / 2f; + float height = 2.65f * radius; + registerCanShape(radius, height); + + float ySpacing = 1f * height; // center-to-center + float y0 = platformTopY + height / 2f; + Vector3f location = new Vector3f(0, y0, 0f); + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + int numCansInRow = numRows - rowIndex; + location.x = -(numCansInRow - 1) * xSpacing / 2f; + for (int j = 0; j < numCansInRow; ++j) { + setUpCan(location); + location.x += xSpacing; + } + location.y += ySpacing; + } + } + + /** + * Set up a single domino as a target. + * + * @param location the desired location (in physics-space coordinates, not + * null, unaffected) + * @param orientation the desired orientation (in physics-space coordinates, + * not null, unaffected) + */ + private void setUpDomino(Vector3f location, Quaternion orientation) { + CollisionShape shape = findShape("domino"); + + float mass = 10f; + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setDebugMeshNormals(MeshNormals.Facet); + body.setPhysicsLocation(location); + body.setPhysicsRotation(orientation); + + setUpTarget(body); + } + + /** + * Set up a row of dominoes, evenly-spaced along the X axis. + * + * @param numDominoes the desired number of dominoes (≥1) + */ + private void setUpDominoRow(int numDominoes) { + float xSpacing = 32f / numDominoes; // center-to-center + float length = 1.6f * xSpacing; + registerDominoShape(length); + + float x0 = -xSpacing * (numDominoes - 1) / 2f; + float y = platformTopY + length / 2; + Vector3f location = new Vector3f(x0, y, 0f); + for (int j = 0; j < numDominoes; ++j) { + setUpDomino(location, Quaternion.IDENTITY); + location.x += xSpacing; + } + } + + /** + * Set up 15 balls in a wedge, as if for a game of Pool. + */ + private void setUpPool() { + int numRows = 5; + float xSpacing = 32f / numRows; // center-to-center + float radius = 0.48f * xSpacing; + registerBallShape(radius); + + float zSpacing = xSpacing / FastMath.sqrt(1.5f); // center-to-center + float z0 = (numRows - 1) * zSpacing / 2f; + Vector3f location = new Vector3f(0, radius, z0); + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + int numBallsInRow = rowIndex + 1; + location.x = -(numBallsInRow - 1) * xSpacing / 2f; + for (int j = 0; j < numBallsInRow; ++j) { + setUpBall(location); + location.x += xSpacing; + } + location.z -= zSpacing; + } + } + + /** + * Set up all targets for selected scenario. + */ + private void setUpScenario() { + String scenarioName = status.scenarioName(); + switch (scenarioName) { + case "brick tower": { + int numRings = 10; + int numBricksPerRing = 16; + float thickness = 2f; + setUpBrickTower(numRings, numBricksPerRing, thickness); + break; + } + + case "brick wall": { + int numRows = 14; + int numBricksPerRow = 10; + setUpBrickWall(numRows, numBricksPerRow); + break; + } + + case "can pyramid": { + int numRows = 15; + setUpCanPyramid(numRows); + break; + } + + case "domino row": { + int numDominoes = 25; + setUpDominoRow(numDominoes); + break; + } + + case "empty": + break; + + case "pool": + setUpPool(); + break; + + case "tenpin": + setUpTenpin(); + break; + + default: + String msg = "scenarioName = " + MyString.quote(scenarioName); + throw new RuntimeException(msg); + } + } + + /** + * Set up a single target body whose position and debug normals have already + * been configured. + * + * @param body the body to set up (not null, not in world) + */ + private void setUpTarget(PhysicsRigidBody body) { + assert body != null; + assert !body.isInWorld(); + + Random random = getGenerator(); + String materialName = "target" + random.nextInt(numTargetColors); + Material debugMaterial = findMaterial(materialName); + body.setApplicationData(debugMaterial); + + addCollisionObject(body); + } + + /** + * Set up 10 bowling pins in a wedge, as if for a game of (ten-pin) bowling. + */ + private void setUpTenpin() { + int numRows = 4; + float xSpacing = 32f / numRows; // center-to-center + float radius = 0.2f * xSpacing; + registerBowlingPinShape(radius); + + float y0 = 2.50f * radius; + float zSpacing = xSpacing / FastMath.sqrt(1.5f); // center-to-center + float z0 = (numRows - 1) * zSpacing / 2f; + Vector3f location = new Vector3f(0, y0, z0); + for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) { + int numPinsInRow = rowIndex + 1; + location.x = -(numPinsInRow - 1) * xSpacing / 2f; + for (int j = 0; j < numPinsInRow; ++j) { + setUpBowlingPin(location); + location.x += xSpacing; + } + location.z -= zSpacing; + } + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemoStatus.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemoStatus.java index 492784725..46d006cfe 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemoStatus.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TargetDemoStatus.java @@ -1,683 +1,683 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.math.ColorRGBA; -import java.util.Arrays; -import java.util.logging.Logger; -import jme3utilities.SimpleAppState; -import jme3utilities.math.MyArray; -import jme3utilities.math.MyMath; -import jme3utilities.ui.AcorusDemo; - -/** - * AppState to display the status of the TargetDemo application in an overlay. - * The overlay consists of status lines, one of which is selected for editing. - * The overlay is located in the upper-left portion of the display. - * - * @author Stephen Gold sgold@sonic.net - */ -class TargetDemoStatus extends SimpleAppState { - // ************************************************************************* - // constants and loggers - - /** - * list of damping fractions, in ascending order - */ - final private static float[] dampingValues - = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; - /** - * list of friction coefficients, in ascending order - */ - final private static float[] frictionValues - = {0f, 0.1f, 0.2f, 0.5f, 1f, 2f, 4f}; - /** - * list of gravity magnitudes, in ascending order - */ - final private static float[] gravityValues - = {1f, 2f, 5f, 10f, 20f, 30f, 50f}; - /** - * list of missile initial speeds, in ascending order - */ - final private static float[] missileInitialSpeedValues - = {10f, 50f, 100f, 200f, 400f, 1000f}; - /** - * list of missile masses, in ascending order - */ - final private static float[] missileMassValues - = {0.1f, 0.3f, 0.5f, 1f, 3f, 10f}; - /** - * list of missile radii, in ascending order - */ - final private static float[] missileRadiusValues - = {0.1f, 0.3f, 0.5f, 1f, 3f, 10f}; - /** - * list of restitution fractions, in ascending order - */ - final private static float[] restitutionValues - = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; - /** - * index of the status line for the damping fraction - */ - final private static int dampingStatusLine = 7; - /** - * index of the status line for the friction coefficient - */ - final private static int frictionStatusLine = 8; - /** - * index of the status line for the gravity magnitude - */ - final private static int gravityStatusLine = 9; - /** - * index of the status line for the missile initial speed - */ - final private static int missileInitialSpeedStatusLine = 6; - /** - * index of the status line for the missile mass - */ - final private static int missileMassStatusLine = 4; - /** - * index of the status line for the missile radius - */ - final private static int missileRadiusStatusLine = 5; - /** - * number of lines of text in the overlay - */ - final private static int numStatusLines = 11; - /** - * index of the status line for the platform name - */ - final private static int platformStatusLine = 2; - /** - * index of the status line for the restitution fraction - */ - final private static int restitutionStatusLine = 10; - /** - * index of the status line for the scenario - */ - final private static int scenarioStatusLine = 3; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TargetDemoStatus.class.getName()); - /** - * names of all platforms, in ascending lexicographic order - */ - final private static String[] platformNames = { - "box", "cone", "cylinder", "hull", "plane", "roundedRectangle", - "square", "triangle" - }; - /** - * list of scenario names, in ascending lexicographic order - */ - final private static String[] scenarioNames = { - "brick tower", "brick wall", "can pyramid", "domino row", "empty", - "pool", "tenpin" - }; - // ************************************************************************* - // fields - - /** - * lines of text displayed in the upper-left corner of the GUI node ([0] is - * the top line) - */ - final private BitmapText[] statusLines = new BitmapText[numStatusLines]; - /** - * flag to enable child coloring for unselected PCOs with compound shapes - */ - private boolean isChildColoring = false; - /** - * flag to force wireframe materials for all unselected PCOs (overrides - * child coloring) - */ - private boolean isWireframe = false; - /** - * damping fraction for all dynamic bodies (≥0, ≤1) - */ - private float damping = 0.6f; - /** - * friction coefficient for all rigid bodies (≥0) - */ - private float friction = 0.5f; - /** - * gravity magnitude for all dynamic bodies (in physics-space units per - * second squared, ≥0) - */ - private float gravity = 30f; - /** - * initial speed selected for the next missile (in physics-space units per - * second, >0) - */ - private float missileInitialSpeed = 200f; - /** - * mass selected for the next missile (>0) - */ - private float missileMass = 0.5f; - /** - * radius selected for the next missile (in physics-space units, >0) - */ - private float missileRadius = 0.5f; - /** - * restitution all all rigid bodies (≥0, ≤1) - */ - private float restitution = 0.3f; - /** - * index of the line being edited (≥1) - */ - private int selectedLine = platformStatusLine; - /** - * name of the platform - */ - private String platformName = "plane"; - /** - * name of the selected scenario - */ - private String scenarioName = "brick wall"; - /** - * reference to the application instance - */ - private TargetDemo appInstance; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized enabled state. - */ - TargetDemoStatus() { - super(true); - } - // ************************************************************************* - // new methods exposed - - /** - * Advance the field selection by the specified amount. - * - * @param amount the number of fields to move downward - */ - void advanceSelectedField(int amount) { - int firstField = 2; - int numFields = numStatusLines - firstField; - - int selectedField = selectedLine - firstField; - int sum = selectedField + amount; - selectedField = MyMath.modulo(sum, numFields); - this.selectedLine = selectedField + firstField; - } - - /** - * Advance the value of the selected field by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - void advanceValue(int amount) { - switch (selectedLine) { - case dampingStatusLine: - advanceDamping(amount); - break; - - case frictionStatusLine: - advanceFriction(amount); - break; - - case gravityStatusLine: - advanceGravity(amount); - break; - - case missileInitialSpeedStatusLine: - advanceMissileInitialSpeed(amount); - break; - - case missileMassStatusLine: - advanceMissileMass(amount); - break; - - case missileRadiusStatusLine: - advanceMissileRadius(amount); - break; - - case platformStatusLine: - advancePlatform(amount); - break; - - case restitutionStatusLine: - advanceRestitution(amount); - break; - - case scenarioStatusLine: - advanceScenario(amount); - break; - - default: - throw new IllegalStateException("line = " + selectedLine); - } - } - - /** - * Determine the selected damping fraction for all dynamic bodies. - * - * @return the fraction (≥0, ≤1) - */ - float damping() { - assert damping >= 0f : damping; - assert damping <= 1f : damping; - return damping; - } - - /** - * Determine the selected friction coefficient for all rigid bodies. - * - * @return the coefficient (≥0) - */ - float friction() { - assert friction >= 0f : friction; - return friction; - } - - /** - * Determine the selected gravity magnitude for all dynamic bodies. - * - * @return the acceleration (in world units per second squared, ≥0) - */ - float gravity() { - assert gravity >= 0f : gravity; - return gravity; - } - - /** - * Test whether child coloring is enabled. - * - * @return true if enabled, otherwise false - */ - boolean isChildColoring() { - return isChildColoring; - } - - /** - * Test whether wireframe materials are enabled. - * - * @return true if enabled, otherwise false - */ - boolean isWireframe() { - return isWireframe; - } - - /** - * Determine the initial speed for a new missile. - * - * @return the speed (in physics-space units per second, >0) - */ - float missileInitialSpeed() { - assert missileInitialSpeed > 0f : missileInitialSpeed; - return missileInitialSpeed; - } - - /** - * Determine the mass for a new missile. - * - * @return the mass (>0) - */ - float missileMass() { - assert missileMass > 0f : missileMass; - return missileMass; - } - - /** - * Determine the radius for a new missile. - * - * @return the radius (in physics-space units, >0) - */ - float missileRadius() { - assert missileRadius > 0f : missileRadius; - return missileRadius; - } - - /** - * Determine the selected type of platform. - * - * @return the name (not null, not empty) - */ - String platformType() { - assert platformName != null; - assert !platformName.isEmpty(); - return platformName; - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - void resize(int newWidth, int newHeight) { - if (isInitialized()) { - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - } - } - - /** - * Determine the selected restitution fraction for all rigid bodies. - * - * @return the fraction (≥0, ≤1) - */ - float restitution() { - assert restitution >= 0f : restitution; - assert restitution <= 1f : restitution; - return restitution; - } - - /** - * Determine the selected scenario. - * - * @return the scenario name (not null, not empty) - */ - String scenarioName() { - assert scenarioName != null; - assert !scenarioName.isEmpty(); - return scenarioName; - } - - /** - * Toggle child coloring disabled/enabled. - */ - void toggleChildColor() { - this.isChildColoring = !isChildColoring; - appInstance.setDebugMaterialsAll(); - } - - /** - * Toggle wireframe disabled/enabled. - */ - void toggleWireframe() { - this.isWireframe = !isWireframe; - appInstance.setDebugMaterialsAll(); - TargetDemo.setDebugShadowMode(); - } - // ************************************************************************* - // ActionAppState methods - - /** - * Clean up this AppState during the first update after it gets detached. - * Should be invoked only by a subclass or by the AppStateManager. - */ - @Override - public void cleanup() { - super.cleanup(); - - // Remove the status lines from the guiNode. - for (int i = 0; i < numStatusLines; ++i) { - statusLines[i].removeFromParent(); - } - } - - /** - * Initialize this AppState on the first update after it gets attached. - * - * @param sm application's state manager (not null) - * @param app application which owns this state (not null) - */ - @Override - public void initialize(AppStateManager sm, Application app) { - super.initialize(sm, app); - - this.appInstance = (TargetDemo) app; - BitmapFont guiFont - = assetManager.loadFont("Interface/Fonts/Default.fnt"); - - // Add status lines to the guiNode. - for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - float y = cam.getHeight() - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - guiNode.attachChild(statusLines[lineIndex]); - } - - assert MyArray.isSorted(dampingValues); - assert MyArray.isSorted(frictionValues); - assert MyArray.isSorted(gravityValues); - assert MyArray.isSorted(missileInitialSpeedValues); - assert MyArray.isSorted(missileMassValues); - assert MyArray.isSorted(missileRadiusValues); - assert MyArray.isSorted(restitutionValues); - - assert MyArray.isSorted(platformNames); - assert MyArray.isSorted(scenarioNames); - } - - /** - * Callback to update this AppState prior to rendering. (Invoked once per - * frame while the state is attached and enabled.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - updateStatusText(); - - int index = 1 + Arrays.binarySearch(dampingValues, damping); - int count = dampingValues.length; - String message = String.format( - "Damping #%d of %d: %.2f", index, count, damping); - updateStatusLine(dampingStatusLine, message); - - index = 1 + Arrays.binarySearch(frictionValues, friction); - count = frictionValues.length; - message = String.format( - "Friction #%d of %d: %.1f", index, count, friction); - updateStatusLine(frictionStatusLine, message); - - index = 1 + Arrays.binarySearch(gravityValues, gravity); - count = gravityValues.length; - message = String.format( - "Gravity #%d of %d: %.1f", index, count, gravity); - updateStatusLine(gravityStatusLine, message); - - index = 1 + Arrays.binarySearch(scenarioNames, scenarioName); - count = scenarioNames.length; - message = String.format( - "Scenario #%d of %d: %s", index, count, scenarioName); - updateStatusLine(scenarioStatusLine, message); - - index = 1 + Arrays.binarySearch(platformNames, platformName); - count = platformNames.length; - message = String.format( - "Platform #%d of %d: %s", index, count, platformName); - updateStatusLine(platformStatusLine, message); - - index = 1 + Arrays.binarySearch(restitutionValues, restitution); - count = restitutionValues.length; - message = String.format( - "Restitution #%d of %d: %.2f", index, count, restitution); - updateStatusLine(restitutionStatusLine, message); - - index = 1 + Arrays.binarySearch( - missileInitialSpeedValues, missileInitialSpeed); - count = missileInitialSpeedValues.length; - message = String.format("Missile speed #%d of %d: %.0f", index, count, - missileInitialSpeed); - updateStatusLine(missileInitialSpeedStatusLine, message); - - index = 1 + Arrays.binarySearch(missileMassValues, missileMass); - count = missileMassValues.length; - message = String.format( - "Missile mass #%d of %d: %.2f", index, count, missileMass); - updateStatusLine(missileMassStatusLine, message); - - index = 1 + Arrays.binarySearch(missileRadiusValues, missileRadius); - count = missileRadiusValues.length; - message = String.format( - "Missile radius #%d of %d: %.2f", index, count, missileRadius); - updateStatusLine(missileRadiusStatusLine, message); - } - // ************************************************************************* - // private methods - - /** - * Advance the damping selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceDamping(int amount) { - this.damping = AcorusDemo.advanceFloat(dampingValues, damping, amount); - appInstance.setDampingAll(damping); - } - - /** - * Advance the friction selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceFriction(int amount) { - this.friction - = AcorusDemo.advanceFloat(frictionValues, friction, amount); - appInstance.setFrictionAll(friction); - } - - /** - * Advance the gravity selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceGravity(int amount) { - this.gravity = AcorusDemo.advanceFloat(gravityValues, gravity, amount); - appInstance.setGravityAll(gravity); - } - - /** - * Advance the initial speed selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceMissileInitialSpeed(int amount) { - this.missileInitialSpeed = AcorusDemo.advanceFloat( - missileInitialSpeedValues, missileInitialSpeed, amount); - } - - /** - * Advance the missile mass selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceMissileMass(int amount) { - this.missileMass = AcorusDemo.advanceFloat( - missileMassValues, missileMass, amount); - } - - /** - * Advance the missile radius selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceMissileRadius(int amount) { - this.missileRadius = AcorusDemo.advanceFloat( - missileRadiusValues, missileRadius, amount); - } - - /** - * Advance the platform selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advancePlatform(int amount) { - this.platformName - = AcorusDemo.advanceString(platformNames, platformName, amount); - appInstance.restartScenario(); - } - - /** - * Advance the restitution selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceRestitution(int amount) { - this.restitution = AcorusDemo.advanceFloat( - restitutionValues, restitution, amount); - appInstance.setRestitutionAll(restitution); - } - - /** - * Advance the scenario selection by the specified amount. - * - * @param amount the number of values to advance (may be negative) - */ - private void advanceScenario(int amount) { - this.scenarioName - = AcorusDemo.advanceString(scenarioNames, scenarioName, amount); - } - - /** - * Update the indexed status line. - * - * @param lineIndex which status line (≥0) - * @param text the text to display, not including the arrow, if any - */ - private void updateStatusLine(int lineIndex, String text) { - BitmapText spatial = statusLines[lineIndex]; - - if (lineIndex == selectedLine) { - spatial.setColor(ColorRGBA.Yellow); - spatial.setText("-> " + text); - } else { - spatial.setColor(ColorRGBA.White); - spatial.setText(" " + text); - } - } - - /** - * Update the status text (top 2 lines). - */ - private void updateStatusText() { - String message = " View: "; - if (isWireframe) { - message += "Wireframe "; - } else if (isChildColoring) { - message += "Lit/ChildColoring "; - } else { - message += "Lit "; - } - String viewOptions = appInstance.describePhysicsDebugOptions(); - message += viewOptions; - statusLines[0].setText(message); - - int numActiveBodies = appInstance.countActive(); - int numCachedMeshes = DebugShapeFactory.countCachedMeshes(); - boolean isPaused = appInstance.isPaused(); - message = String.format("activeBodies=%d cachedMeshes=%d%s", - numActiveBodies, numCachedMeshes, isPaused ? " PAUSED" : ""); - statusLines[1].setText(message); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.math.ColorRGBA; +import java.util.Arrays; +import java.util.logging.Logger; +import jme3utilities.SimpleAppState; +import jme3utilities.math.MyArray; +import jme3utilities.math.MyMath; +import jme3utilities.ui.AcorusDemo; + +/** + * AppState to display the status of the TargetDemo application in an overlay. + * The overlay consists of status lines, one of which is selected for editing. + * The overlay is located in the upper-left portion of the display. + * + * @author Stephen Gold sgold@sonic.net + */ +class TargetDemoStatus extends SimpleAppState { + // ************************************************************************* + // constants and loggers + + /** + * list of damping fractions, in ascending order + */ + final private static float[] dampingValues + = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; + /** + * list of friction coefficients, in ascending order + */ + final private static float[] frictionValues + = {0f, 0.1f, 0.2f, 0.5f, 1f, 2f, 4f}; + /** + * list of gravity magnitudes, in ascending order + */ + final private static float[] gravityValues + = {1f, 2f, 5f, 10f, 20f, 30f, 50f}; + /** + * list of missile initial speeds, in ascending order + */ + final private static float[] missileInitialSpeedValues + = {10f, 50f, 100f, 200f, 400f, 1000f}; + /** + * list of missile masses, in ascending order + */ + final private static float[] missileMassValues + = {0.1f, 0.3f, 0.5f, 1f, 3f, 10f}; + /** + * list of missile radii, in ascending order + */ + final private static float[] missileRadiusValues + = {0.1f, 0.3f, 0.5f, 1f, 3f, 10f}; + /** + * list of restitution fractions, in ascending order + */ + final private static float[] restitutionValues + = {0f, 0.1f, 0.3f, 0.6f, 0.9f, 0.99f}; + /** + * index of the status line for the damping fraction + */ + final private static int dampingStatusLine = 7; + /** + * index of the status line for the friction coefficient + */ + final private static int frictionStatusLine = 8; + /** + * index of the status line for the gravity magnitude + */ + final private static int gravityStatusLine = 9; + /** + * index of the status line for the missile initial speed + */ + final private static int missileInitialSpeedStatusLine = 6; + /** + * index of the status line for the missile mass + */ + final private static int missileMassStatusLine = 4; + /** + * index of the status line for the missile radius + */ + final private static int missileRadiusStatusLine = 5; + /** + * number of lines of text in the overlay + */ + final private static int numStatusLines = 11; + /** + * index of the status line for the platform name + */ + final private static int platformStatusLine = 2; + /** + * index of the status line for the restitution fraction + */ + final private static int restitutionStatusLine = 10; + /** + * index of the status line for the scenario + */ + final private static int scenarioStatusLine = 3; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TargetDemoStatus.class.getName()); + /** + * names of all platforms, in ascending lexicographic order + */ + final private static String[] platformNames = { + "box", "cone", "cylinder", "hull", "plane", "roundedRectangle", + "square", "triangle" + }; + /** + * list of scenario names, in ascending lexicographic order + */ + final private static String[] scenarioNames = { + "brick tower", "brick wall", "can pyramid", "domino row", "empty", + "pool", "tenpin" + }; + // ************************************************************************* + // fields + + /** + * lines of text displayed in the upper-left corner of the GUI node ([0] is + * the top line) + */ + final private BitmapText[] statusLines = new BitmapText[numStatusLines]; + /** + * flag to enable child coloring for unselected PCOs with compound shapes + */ + private boolean isChildColoring = false; + /** + * flag to force wireframe materials for all unselected PCOs (overrides + * child coloring) + */ + private boolean isWireframe = false; + /** + * damping fraction for all dynamic bodies (≥0, ≤1) + */ + private float damping = 0.6f; + /** + * friction coefficient for all rigid bodies (≥0) + */ + private float friction = 0.5f; + /** + * gravity magnitude for all dynamic bodies (in physics-space units per + * second squared, ≥0) + */ + private float gravity = 30f; + /** + * initial speed selected for the next missile (in physics-space units per + * second, >0) + */ + private float missileInitialSpeed = 200f; + /** + * mass selected for the next missile (>0) + */ + private float missileMass = 0.5f; + /** + * radius selected for the next missile (in physics-space units, >0) + */ + private float missileRadius = 0.5f; + /** + * restitution all all rigid bodies (≥0, ≤1) + */ + private float restitution = 0.3f; + /** + * index of the line being edited (≥1) + */ + private int selectedLine = platformStatusLine; + /** + * name of the platform + */ + private String platformName = "plane"; + /** + * name of the selected scenario + */ + private String scenarioName = "brick wall"; + /** + * reference to the application instance + */ + private TargetDemo appInstance; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized enabled state. + */ + TargetDemoStatus() { + super(true); + } + // ************************************************************************* + // new methods exposed + + /** + * Advance the field selection by the specified amount. + * + * @param amount the number of fields to move downward + */ + void advanceSelectedField(int amount) { + int firstField = 2; + int numFields = numStatusLines - firstField; + + int selectedField = selectedLine - firstField; + int sum = selectedField + amount; + selectedField = MyMath.modulo(sum, numFields); + this.selectedLine = selectedField + firstField; + } + + /** + * Advance the value of the selected field by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + void advanceValue(int amount) { + switch (selectedLine) { + case dampingStatusLine: + advanceDamping(amount); + break; + + case frictionStatusLine: + advanceFriction(amount); + break; + + case gravityStatusLine: + advanceGravity(amount); + break; + + case missileInitialSpeedStatusLine: + advanceMissileInitialSpeed(amount); + break; + + case missileMassStatusLine: + advanceMissileMass(amount); + break; + + case missileRadiusStatusLine: + advanceMissileRadius(amount); + break; + + case platformStatusLine: + advancePlatform(amount); + break; + + case restitutionStatusLine: + advanceRestitution(amount); + break; + + case scenarioStatusLine: + advanceScenario(amount); + break; + + default: + throw new IllegalStateException("line = " + selectedLine); + } + } + + /** + * Determine the selected damping fraction for all dynamic bodies. + * + * @return the fraction (≥0, ≤1) + */ + float damping() { + assert damping >= 0f : damping; + assert damping <= 1f : damping; + return damping; + } + + /** + * Determine the selected friction coefficient for all rigid bodies. + * + * @return the coefficient (≥0) + */ + float friction() { + assert friction >= 0f : friction; + return friction; + } + + /** + * Determine the selected gravity magnitude for all dynamic bodies. + * + * @return the acceleration (in world units per second squared, ≥0) + */ + float gravity() { + assert gravity >= 0f : gravity; + return gravity; + } + + /** + * Test whether child coloring is enabled. + * + * @return true if enabled, otherwise false + */ + boolean isChildColoring() { + return isChildColoring; + } + + /** + * Test whether wireframe materials are enabled. + * + * @return true if enabled, otherwise false + */ + boolean isWireframe() { + return isWireframe; + } + + /** + * Determine the initial speed for a new missile. + * + * @return the speed (in physics-space units per second, >0) + */ + float missileInitialSpeed() { + assert missileInitialSpeed > 0f : missileInitialSpeed; + return missileInitialSpeed; + } + + /** + * Determine the mass for a new missile. + * + * @return the mass (>0) + */ + float missileMass() { + assert missileMass > 0f : missileMass; + return missileMass; + } + + /** + * Determine the radius for a new missile. + * + * @return the radius (in physics-space units, >0) + */ + float missileRadius() { + assert missileRadius > 0f : missileRadius; + return missileRadius; + } + + /** + * Determine the selected type of platform. + * + * @return the name (not null, not empty) + */ + String platformType() { + assert platformName != null; + assert !platformName.isEmpty(); + return platformName; + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + void resize(int newWidth, int newHeight) { + if (isInitialized()) { + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + } + } + + /** + * Determine the selected restitution fraction for all rigid bodies. + * + * @return the fraction (≥0, ≤1) + */ + float restitution() { + assert restitution >= 0f : restitution; + assert restitution <= 1f : restitution; + return restitution; + } + + /** + * Determine the selected scenario. + * + * @return the scenario name (not null, not empty) + */ + String scenarioName() { + assert scenarioName != null; + assert !scenarioName.isEmpty(); + return scenarioName; + } + + /** + * Toggle child coloring disabled/enabled. + */ + void toggleChildColor() { + this.isChildColoring = !isChildColoring; + appInstance.setDebugMaterialsAll(); + } + + /** + * Toggle wireframe disabled/enabled. + */ + void toggleWireframe() { + this.isWireframe = !isWireframe; + appInstance.setDebugMaterialsAll(); + TargetDemo.setDebugShadowMode(); + } + // ************************************************************************* + // ActionAppState methods + + /** + * Clean up this AppState during the first update after it gets detached. + * Should be invoked only by a subclass or by the AppStateManager. + */ + @Override + public void cleanup() { + super.cleanup(); + + // Remove the status lines from the guiNode. + for (int i = 0; i < numStatusLines; ++i) { + statusLines[i].removeFromParent(); + } + } + + /** + * Initialize this AppState on the first update after it gets attached. + * + * @param sm application's state manager (not null) + * @param app application which owns this state (not null) + */ + @Override + public void initialize(AppStateManager sm, Application app) { + super.initialize(sm, app); + + this.appInstance = (TargetDemo) app; + BitmapFont guiFont + = assetManager.loadFont("Interface/Fonts/Default.fnt"); + + // Add status lines to the guiNode. + for (int lineIndex = 0; lineIndex < numStatusLines; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + float y = cam.getHeight() - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + guiNode.attachChild(statusLines[lineIndex]); + } + + assert MyArray.isSorted(dampingValues); + assert MyArray.isSorted(frictionValues); + assert MyArray.isSorted(gravityValues); + assert MyArray.isSorted(missileInitialSpeedValues); + assert MyArray.isSorted(missileMassValues); + assert MyArray.isSorted(missileRadiusValues); + assert MyArray.isSorted(restitutionValues); + + assert MyArray.isSorted(platformNames); + assert MyArray.isSorted(scenarioNames); + } + + /** + * Callback to update this AppState prior to rendering. (Invoked once per + * frame while the state is attached and enabled.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + updateStatusText(); + + int index = 1 + Arrays.binarySearch(dampingValues, damping); + int count = dampingValues.length; + String message = String.format( + "Damping #%d of %d: %.2f", index, count, damping); + updateStatusLine(dampingStatusLine, message); + + index = 1 + Arrays.binarySearch(frictionValues, friction); + count = frictionValues.length; + message = String.format( + "Friction #%d of %d: %.1f", index, count, friction); + updateStatusLine(frictionStatusLine, message); + + index = 1 + Arrays.binarySearch(gravityValues, gravity); + count = gravityValues.length; + message = String.format( + "Gravity #%d of %d: %.1f", index, count, gravity); + updateStatusLine(gravityStatusLine, message); + + index = 1 + Arrays.binarySearch(scenarioNames, scenarioName); + count = scenarioNames.length; + message = String.format( + "Scenario #%d of %d: %s", index, count, scenarioName); + updateStatusLine(scenarioStatusLine, message); + + index = 1 + Arrays.binarySearch(platformNames, platformName); + count = platformNames.length; + message = String.format( + "Platform #%d of %d: %s", index, count, platformName); + updateStatusLine(platformStatusLine, message); + + index = 1 + Arrays.binarySearch(restitutionValues, restitution); + count = restitutionValues.length; + message = String.format( + "Restitution #%d of %d: %.2f", index, count, restitution); + updateStatusLine(restitutionStatusLine, message); + + index = 1 + Arrays.binarySearch( + missileInitialSpeedValues, missileInitialSpeed); + count = missileInitialSpeedValues.length; + message = String.format("Missile speed #%d of %d: %.0f", index, count, + missileInitialSpeed); + updateStatusLine(missileInitialSpeedStatusLine, message); + + index = 1 + Arrays.binarySearch(missileMassValues, missileMass); + count = missileMassValues.length; + message = String.format( + "Missile mass #%d of %d: %.2f", index, count, missileMass); + updateStatusLine(missileMassStatusLine, message); + + index = 1 + Arrays.binarySearch(missileRadiusValues, missileRadius); + count = missileRadiusValues.length; + message = String.format( + "Missile radius #%d of %d: %.2f", index, count, missileRadius); + updateStatusLine(missileRadiusStatusLine, message); + } + // ************************************************************************* + // private methods + + /** + * Advance the damping selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceDamping(int amount) { + this.damping = AcorusDemo.advanceFloat(dampingValues, damping, amount); + appInstance.setDampingAll(damping); + } + + /** + * Advance the friction selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceFriction(int amount) { + this.friction + = AcorusDemo.advanceFloat(frictionValues, friction, amount); + appInstance.setFrictionAll(friction); + } + + /** + * Advance the gravity selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceGravity(int amount) { + this.gravity = AcorusDemo.advanceFloat(gravityValues, gravity, amount); + appInstance.setGravityAll(gravity); + } + + /** + * Advance the initial speed selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceMissileInitialSpeed(int amount) { + this.missileInitialSpeed = AcorusDemo.advanceFloat( + missileInitialSpeedValues, missileInitialSpeed, amount); + } + + /** + * Advance the missile mass selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceMissileMass(int amount) { + this.missileMass = AcorusDemo.advanceFloat( + missileMassValues, missileMass, amount); + } + + /** + * Advance the missile radius selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceMissileRadius(int amount) { + this.missileRadius = AcorusDemo.advanceFloat( + missileRadiusValues, missileRadius, amount); + } + + /** + * Advance the platform selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advancePlatform(int amount) { + this.platformName + = AcorusDemo.advanceString(platformNames, platformName, amount); + appInstance.restartScenario(); + } + + /** + * Advance the restitution selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceRestitution(int amount) { + this.restitution = AcorusDemo.advanceFloat( + restitutionValues, restitution, amount); + appInstance.setRestitutionAll(restitution); + } + + /** + * Advance the scenario selection by the specified amount. + * + * @param amount the number of values to advance (may be negative) + */ + private void advanceScenario(int amount) { + this.scenarioName + = AcorusDemo.advanceString(scenarioNames, scenarioName, amount); + } + + /** + * Update the indexed status line. + * + * @param lineIndex which status line (≥0) + * @param text the text to display, not including the arrow, if any + */ + private void updateStatusLine(int lineIndex, String text) { + BitmapText spatial = statusLines[lineIndex]; + + if (lineIndex == selectedLine) { + spatial.setColor(ColorRGBA.Yellow); + spatial.setText("-> " + text); + } else { + spatial.setColor(ColorRGBA.White); + spatial.setText(" " + text); + } + } + + /** + * Update the status text (top 2 lines). + */ + private void updateStatusText() { + String message = " View: "; + if (isWireframe) { + message += "Wireframe "; + } else if (isChildColoring) { + message += "Lit/ChildColoring "; + } else { + message += "Lit "; + } + String viewOptions = appInstance.describePhysicsDebugOptions(); + message += viewOptions; + statusLines[0].setText(message); + + int numActiveBodies = appInstance.countActive(); + int numCachedMeshes = DebugShapeFactory.countCachedMeshes(); + boolean isPaused = appInstance.isPaused(); + message = String.format("activeBodies=%d cachedMeshes=%d%s", + numActiveBodies, numCachedMeshes, isPaused ? " PAUSED" : ""); + statusLines[1].setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TestDac.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TestDac.java index e259a5d96..ab3827d4b 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TestDac.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TestDac.java @@ -1,1187 +1,1187 @@ -/* - Copyright (c) 2018-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.anim.AnimClip; -import com.jme3.anim.AnimComposer; -import com.jme3.anim.SkinningControl; -import com.jme3.anim.util.AnimMigrationUtils; -import com.jme3.app.Application; -import com.jme3.app.StatsAppState; -import com.jme3.asset.AssetNotFoundException; -import com.jme3.asset.ModelKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.AttachmentLink; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.bullet.animation.TorsoLink; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.export.JmeExporter; -import com.jme3.export.binary.BinaryExporter; -import com.jme3.export.xml.XMLExporter; -import com.jme3.font.BitmapText; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.system.AppSettings; -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InfluenceUtil; -import jme3utilities.MyAsset; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.NameGenerator; -import jme3utilities.debug.SkeletonVisualizer; -import jme3utilities.math.noise.Generator; -import jme3utilities.mesh.Icosphere; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.tunings.BaseMeshControl; -import jme3utilities.minie.test.tunings.Biped; -import jme3utilities.minie.test.tunings.ElephantControl; -import jme3utilities.minie.test.tunings.JaimeControl; -import jme3utilities.minie.test.tunings.MhGameControl; -import jme3utilities.minie.test.tunings.NinjaControl; -import jme3utilities.minie.test.tunings.OtoControl; -import jme3utilities.minie.test.tunings.PuppetControl; -import jme3utilities.minie.test.tunings.SinbadControl; -import jme3utilities.ui.ActionApplication; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.Signals; -import jme3utilities.wes.AnimationEdit; -import jme3utilities.wes.Pose; - -/** - * Test scaling and load/save on a DynamicAnimControl. - *

- * Seen in the October 2018 demo video: - * https://www.youtube.com/watch?v=A1Rii99nb3Q - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestDac extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * radius of each falling ball (in mesh units) - */ - final private static float ballRadius = 1f; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestDac.class.getName()); - /** - * asset path for saving J3O - */ - final private static String saveAssetPath = "TestDac.j3o"; - /** - * first asset path for saving XML - */ - final private static String saveAssetPath1 = "TestDac_1.xml"; - /** - * 2nd asset path for saving XML - */ - final private static String saveAssetPath2 = "TestDac_2.xml"; - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName = TestDac.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed in the upper-left corner of the GUI node - */ - private static BitmapText statusText; - /** - * important linked bones - */ - private static BoneLink leftClavicle; - private static BoneLink leftFemur; - private static BoneLink leftUlna; - private static BoneLink rightClavicle; - private static BoneLink rightFemur; - private static BoneLink upperBody; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * Control being tested - */ - private static DynamicAnimControl dac; - /** - * Mesh for falling balls - */ - final private static Mesh ballMesh = new Icosphere(2, ballRadius); - /** - * generate names for falling balls - */ - final private static NameGenerator nameGenerator = new NameGenerator(); - /** - * root node of the C-G model on which the Control is being tested - */ - private static Node cgModel; - /** - * animation pose, or null if not in use - */ - private static Pose animPose; - /** - * visualizer for the skeleton of the C-G model - */ - private static SkeletonVisualizer sv; - /** - * SkinningControl of the loaded model - */ - private static SkinningControl sc; - /** - * name of the Animation/Action to play on the C-G model - */ - private static String animationName = null; - /** - * name the important linked bones - */ - private static String leftClavicleName; - private static String leftUlnaName; - private static String rightClavicleName; - private static String upperBodyName; - /** - * name of the test (the model that's loaded) - */ - private static String testName = ""; - /** - * C-G model's local transform when initially loaded - */ - private static Transform resetTransform; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestDac application. - */ - public TestDac() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestDac application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - - Application application = new TestDac(); - application.setSettings(settings); - /* - * Designate a sandbox directory. - * This must be done *prior to* initialization. - */ - try { - ActionApplication.designateSandbox("Written Assets"); - } catch (IOException exception) { - // do nothing - } - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - guiNode.attachChild(statusText); - - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - addLighting(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - float halfExtent = 250f; - float topY = 0f; - attachCubePlatform(halfExtent, topY); - - ColorRGBA ballColor = new ColorRGBA(0.4f, 0f, 0f, 1f); - Material ballMaterial - = MyAsset.createShinyMaterial(assetManager, ballColor); - ballMaterial.setFloat("Shininess", 5f); - registerMaterial("ball", ballMaterial); - - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - registerShape("ball", ballShape); - - addModel("Sinbad"); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - super.configureDumper(); - - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.JointsInSpaces, true); - } - - /** - * Calculate screen bounds for the detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes below the status. - float margin = 10f; // in pixels - float leftX = margin; - float topY = viewPortHeight - 20f - margin; - float width = viewPortWidth - leftX - margin; - float height = topY - margin; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind("add", KeyInput.KEY_INSERT); - dim.bind("amputate left elbow", KeyInput.KEY_DELETE); - dim.bind("blend all to kinematic", KeyInput.KEY_K); - dim.bind(asCollectGarbage, KeyInput.KEY_G); - dim.bind("drop attachments", KeyInput.KEY_PGDN); - dim.bind(asDumpScenes, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bind("freeze all", KeyInput.KEY_F); - dim.bind("freeze upper body", KeyInput.KEY_U); - dim.bind("ghost upper body", KeyInput.KEY_8); - dim.bind("go anim pose", KeyInput.KEY_6); - dim.bind("go bind pose", KeyInput.KEY_B); - dim.bind("go dynamic bind pose", KeyInput.KEY_7); - dim.bind("go floating", KeyInput.KEY_0); - dim.bind("go frozen", KeyInput.KEY_MINUS); - dim.bind("go limp", KeyInput.KEY_SPACE); - dim.bind("limp left arm", KeyInput.KEY_LBRACKET); - dim.bind("limp right arm", KeyInput.KEY_RBRACKET); - dim.bind("load", KeyInput.KEY_L); - - dim.bind("load BaseMesh", KeyInput.KEY_F11); - dim.bind("load Elephant", KeyInput.KEY_F3); - dim.bind("load Jaime", KeyInput.KEY_F2); - dim.bind("load MhGame", KeyInput.KEY_F9); - dim.bind("load Ninja", KeyInput.KEY_F7); - dim.bind("load Oto", KeyInput.KEY_F6); - dim.bind("load Puppet", KeyInput.KEY_F8); - dim.bind("load Sinbad", KeyInput.KEY_F1); - dim.bind("load SinbadWith1Sword", KeyInput.KEY_F10); - dim.bind("load SinbadWithSwords", KeyInput.KEY_F4); - - dim.bind("pin leftFemur", KeyInput.KEY_9); - dim.bind("raise leftFoot", KeyInput.KEY_LCONTROL); - dim.bind("raise leftHand", KeyInput.KEY_LSHIFT); - dim.bind("raise rightFoot", KeyInput.KEY_RCONTROL); - dim.bind("raise rightHand", KeyInput.KEY_RSHIFT); - - dim.bind("reset model transform", KeyInput.KEY_HOME); - dim.bind("save", KeyInput.KEY_COMMA); - dim.bind("set height 1", KeyInput.KEY_1); - dim.bind("set height 2", KeyInput.KEY_2); - dim.bind("set height 3", KeyInput.KEY_3); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("rotateLeft", KeyInput.KEY_LEFT); - dim.bindSignal("rotateRight", KeyInput.KEY_RIGHT); - dim.bindSignal("shower", KeyInput.KEY_I); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleDebug, KeyInput.KEY_SLASH); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind("toggle meshes", KeyInput.KEY_M); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind("toggle skeleton", KeyInput.KEY_V); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "add": - addBall(); - return; - case "amputate left elbow": - dac.amputateSubtree(leftUlna, 2f); - return; - case "blend all to kinematic": - animPose = null; - dac.blendToKinematicMode(2f, null); - return; - case "drop attachments": - dac.dropAttachments(); - return; - - case "freeze all": - animPose = null; - dac.freezeSubtree(dac.getTorsoLink(), false); - return; - case "freeze upper body": - dac.freezeSubtree(upperBody, false); - return; - case "ghost upper body": - dac.setContactResponseSubtree(upperBody, false); - return; - - case "go anim pose": - goAnimPose(); - return; - case "go bind pose": - animPose = null; - dac.bindSubtree(dac.getTorsoLink(), 2f); - return; - case "go dynamic bind pose": - if (dac.isReady()) { - goDynamicBindPose(); - } - return; - case "go floating": - if (dac.isReady()) { - animPose = null; - dac.setDynamicSubtree( - dac.getTorsoLink(), Vector3f.ZERO, false); - } - return; - case "go frozen": - if (dac.isReady()) { - animPose = null; - Vector3f gravity = dac.gravity(null); - dac.setDynamicSubtree( - dac.getTorsoLink(), gravity, true); - } - return; - case "go limp": - if (dac.isReady()) { - animPose = null; - dac.setRagdollMode(); - } - return; - - case "limp left arm": - if (dac.isReady()) { - dac.setDynamicSubtree(leftClavicle, - new Vector3f(0f, -150f, 0f), false); - } - return; - case "limp right arm": - if (dac.isReady()) { - dac.setDynamicSubtree(rightClavicle, - new Vector3f(0f, -150f, 0f), false); - } - return; - - case "load": - load(); - return; - - case "pin leftFemur": - if (dac.isReady()) { - boolean disableForRagdoll = false; - dac.pinToWorld(leftFemur, disableForRagdoll); - } - return; - - case "raise leftFoot": - if (dac.isReady()) { - dac.setDynamicSubtree( - leftFemur, new Vector3f(0f, 100f, 0f), false); - } - return; - case "raise leftHand": - if (dac.isReady()) { - dac.setDynamicSubtree(leftClavicle, - new Vector3f(0f, 100f, 0f), false); - } - return; - case "raise rightFoot": - if (dac.isReady()) { - dac.setDynamicSubtree( - rightFemur, new Vector3f(0f, 100f, 0f), false); - } - return; - case "raise rightHand": - if (dac.isReady()) { - dac.setDynamicSubtree(rightClavicle, - new Vector3f(0f, 100f, 0f), false); - } - return; - - case "reset model transform": - cgModel.setLocalTransform(resetTransform); - return; - case "save": - save(cgModel, saveAssetPath); - save(cgModel, saveAssetPath1); - return; - case "set height 1": - setHeight(5f); - return; - case "set height 2": - setHeight(10f); - return; - case "set height 3": - setHeight(15f); - return; - - case "toggle meshes": - toggleMeshes(); - return; - case "toggle skeleton": - toggleSkeleton(); - return; - - default: - } - String[] words = actionString.split(" "); - if (words.length >= 2 && "load".equals(words[0])) { - addModel(words[1]); - return; - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - statusText.setLocalTranslation(0f, newHeight, 0f); - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - Signals signals = getSignals(); - if (signals.test("shower")) { - addBall(); - } - - float rotateAngle = 0f; - if (signals.test("rotateRight")) { - rotateAngle += tpf; - } - if (signals.test("rotateLeft")) { - rotateAngle -= tpf; - } - if (rotateAngle != 0f) { - rotateAngle /= speed; - Quaternion orientation = MySpatial.worldOrientation(cgModel, null); - Quaternion rotate - = new Quaternion().fromAngles(0f, rotateAngle, 0f); - rotate.mult(orientation, orientation); - MySpatial.setWorldOrientation(cgModel, orientation); - } - - if (animPose != null) { - matchAnimPose(); - } - - updateStatusText(); - } - // ************************************************************************* - // private methods - - /** - * Add a falling ball to the scene. - */ - private void addBall() { - String name = nameGenerator.unique("ball"); - Geometry geometry = new Geometry(name, ballMesh); - rootNode.attachChild(geometry); - - Material material = findMaterial("ball"); - geometry.setMaterial(material); - geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - Generator random = getGenerator(); - Vector3f location = random.nextVector3f(); - location.multLocal(2.5f, 5f, 2.5f); - location.y += 20f; - geometry.move(location); - - Vector3f worldScale = geometry.getWorldScale(); // alias - CollisionShape shape = findShape("ball"); - shape.setScale(worldScale); - - float mass = 12f; - RigidBodyControl rbc = new RigidBodyControl(shape, mass); - rbc.setApplyScale(true); - rbc.setLinearVelocity(new Vector3f(0f, -5f, 0f)); - rbc.setKinematic(false); - rbc.setFriction(10f); - - addCollisionObject(rbc); - rbc.setGravity(new Vector3f(0f, -1.5f, 0f)); - - geometry.addControl(rbc); - } - - /** - * Add lighting and shadows to the main scene. - */ - private void addLighting() { - ColorRGBA ambientColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootNode.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - sun.setName("sun"); - - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.5f); - viewPort.addProcessor(dlsr); - } - - /** - * Add an animated model to the scene, removing any previously added model. - * - * @param modelName the name of the model to add (not null, not empty) - */ - private void addModel(String modelName) { - if (cgModel != null) { - dac.getSpatial().removeControl(dac); - rootNode.detachChild(cgModel); - rootNode.removeControl(sv); - removeAllBalls(); - } - - switch (modelName) { - case "BaseMesh": - loadBaseMesh(); - break; - case "Elephant": - loadElephant(); - break; - case "Jaime": - loadJaime(); - break; - case "MhGame": - loadMhGame(); - break; - case "Ninja": - loadNinja(); - break; - case "Oto": - loadOto(); - break; - case "Puppet": - loadPuppet(); - break; - case "Sinbad": - loadSinbad(); - break; - case "SinbadWith1Sword": - loadSinbadWith1Sword(); - break; - case "SinbadWithSwords": - loadSinbadWithSwords(); - break; - default: - throw new IllegalArgumentException(modelName); - } - - testName = modelName; - animPose = null; - - List list = MySpatial.listSpatials(cgModel); - for (Spatial spatial : list) { - spatial.setShadowMode(RenderQueue.ShadowMode.Cast); - } - cgModel.setCullHint(Spatial.CullHint.Never); - - rootNode.attachChild(cgModel); - setCgmHeight(cgModel, 10f); - centerCgm(cgModel); - resetTransform = cgModel.getLocalTransform().clone(); - - sc = (SkinningControl) RagUtils.findSControl(cgModel); - Spatial controlledSpatial = sc.getSpatial(); - - controlledSpatial.addControl(dac); - dac.setGravity(new Vector3f(0f, -50f, 0f)); - PhysicsSpace physicsSpace = getPhysicsSpace(); - dac.setPhysicsSpace(physicsSpace); - - leftClavicle = dac.findBoneLink(leftClavicleName); - if (dac instanceof Biped) { - BoneLink leftFoot = ((Biped) dac).getLeftFoot(); - leftFemur = leftFoot; - while (leftFemur.getParent() instanceof BoneLink) { - leftFemur = (BoneLink) leftFemur.getParent(); - } - - BoneLink rightFoot = ((Biped) dac).getRightFoot(); - rightFemur = rightFoot; - while (rightFemur.getParent() instanceof BoneLink) { - rightFemur = (BoneLink) rightFemur.getParent(); - } - - } else if (dac instanceof ElephantControl) { - leftFemur = dac.findBoneLink("Oberschenkel_B_L"); - rightFemur = dac.findBoneLink("Oberschenkel_B_R"); - } - - leftUlna = dac.findBoneLink(leftUlnaName); - rightClavicle = dac.findBoneLink(rightClavicleName); - upperBody = dac.findBoneLink(upperBodyName); - - AnimComposer composer - = controlledSpatial.getControl(AnimComposer.class); - composer.setCurrentAction(animationName); - - sv = new SkeletonVisualizer(assetManager, sc); - sv.setLineColor(ColorRGBA.Yellow); - InfluenceUtil.hideNonInfluencers(sv, sc); - rootNode.addControl(sv); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(20f); - flyCam.setZoomSpeed(20f); - - cam.setLocation(new Vector3f(0f, 6f, 25f)); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.setAccuracy(0.01f); // 10-msec timestep - physicsSpace.getSolverInfo().setNumIterations(15); - } - - /** - * Put all physics links into zero-g dynamic mode, fix the torso, and enable - * pose matching. - */ - private static void goAnimPose() { - TorsoLink torsoLink = dac.getTorsoLink(); - torsoLink.setDynamic(Vector3f.ZERO); - boolean disableForRagdoll = true; - dac.fixToWorld(torsoLink, disableForRagdoll); - - animPose = Pose.newInstance(sc); - matchAnimPose(); - } - - /** - * Put all bone/torso links into zero-g dynamic mode and lock all physics - * joints at bind pose. - */ - private static void goDynamicBindPose() { - animPose = null; - - TorsoLink torsoLink = dac.getTorsoLink(); - torsoLink.setDynamic(Vector3f.ZERO); - - for (BoneLink boneLink : dac.listLinks(BoneLink.class)) { - boneLink.setDynamic(Vector3f.ZERO, Quaternion.IDENTITY); - } - } - - /** - * Load the saved model from the J3O file. - */ - private void load() { - ModelKey key = new ModelKey(saveAssetPath); - - // Remove any copy from the asset manager's cache. - assetManager.deleteFromCache(key); - - Spatial loadedScene; - try { - loadedScene = assetManager.loadAsset(key); - } catch (AssetNotFoundException exception) { - logger.log(Level.SEVERE, "Didn''t find asset {0}", - MyString.quote(saveAssetPath)); - return; - } - logger.log(Level.INFO, "Loaded {0} from asset {1}", new Object[]{ - MyString.quote(loadedScene.getName()), - MyString.quote(saveAssetPath) - }); - - save(loadedScene, saveAssetPath2); - } - - /** - * Load the BaseMesh model. - */ - private void loadBaseMesh() { - cgModel = (Node) assetManager.loadModel("Models/BaseMesh/BaseMesh.j3o"); - dac = new BaseMeshControl(); - animationName = "run_01"; - leftClavicleName = "spine2_L.001"; - leftUlnaName = "spine2_L.002"; - rightClavicleName = "spine2_R.001"; - upperBodyName = "spine2"; - /* - * Normalize all quaternions in the animation to resolve some issues - * with the BaseMesh model. - */ - Spatial spatial = RagUtils.findSControl(cgModel).getSpatial(); - AnimComposer composer = spatial.getControl(AnimComposer.class); - AnimClip clip = composer.getAnimClip(animationName); - AnimationEdit.normalizeQuaternions(clip, 0.0001f); - } - - /** - * Load the Elephant model. - */ - private void loadElephant() { - cgModel = (Node) assetManager.loadModel("Models/Elephant/Elephant.j3o"); - cgModel.rotate(0f, 1.6f, 0f); - dac = new ElephantControl(); - animationName = "legUp"; - leftClavicleName = "Oberschenkel_F_L"; - leftUlnaName = "Knee_F_L"; - rightClavicleName = "Oberschenkel_F_R"; - upperBodyName = "joint5"; - } - - /** - * Load the Jaime model. - */ - private void loadJaime() { - cgModel = (Node) assetManager.loadModel("Models/Jaime/Jaime-new.j3o"); - Geometry g = (Geometry) cgModel.getChild(0); - RenderState rs = g.getMaterial().getAdditionalRenderState(); - rs.setFaceCullMode(RenderState.FaceCullMode.Off); - - dac = new JaimeControl(); - animationName = "Punches"; - leftClavicleName = "shoulder.L"; - leftUlnaName = "forearm.L"; - rightClavicleName = "shoulder.R"; - upperBodyName = "ribs"; - } - - /** - * Load the MhGame model. - */ - private void loadMhGame() { - cgModel = (Node) assetManager.loadModel("Models/MhGame/MhGame.j3o"); - dac = new MhGameControl(); - animationName = "expr-lib-pose"; - leftClavicleName = "upperarm_l"; - leftUlnaName = "lowerarm_l"; - rightClavicleName = "upperarm_r"; - upperBodyName = "spine_01"; - } - - /** - * Load the Ninja model. - */ - private void loadNinja() { - cgModel = (Node) assetManager.loadModel("Models/Ninja/Ninja.j3o"); - cgModel.rotate(0f, 3f, 0f); - dac = new NinjaControl(); - animationName = "Walk"; - leftClavicleName = "Joint14"; - leftUlnaName = "Joint16"; - rightClavicleName = "Joint9"; - upperBodyName = "Joint4"; - } - - /** - * Load the Oto model. - */ - private void loadOto() { - cgModel = (Node) assetManager.loadModel("Models/Oto/Oto.j3o"); - dac = new OtoControl(); - animationName = "Walk"; - leftClavicleName = "uparm.left"; - leftUlnaName = "arm.left"; - rightClavicleName = "uparm.right"; - upperBodyName = "spinehigh"; - } - - /** - * Load the Puppet model. - */ - private void loadPuppet() { - cgModel = (Node) assetManager.loadModel("Models/Puppet/Puppet.j3o"); - AnimMigrationUtils.migrate(cgModel); - dac = new PuppetControl(); - animationName = "walk"; - leftClavicleName = "upper_arm.1.L"; - leftUlnaName = "forearm.1.L"; - rightClavicleName = "upper_arm.1.R"; - upperBodyName = "spine"; - } - - /** - * Load the Sinbad model without attachments. - */ - private void loadSinbad() { - cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); - dac = new SinbadControl(); - animationName = "Dance"; - leftClavicleName = "Clavicle.L"; - leftUlnaName = "Ulna.L"; - rightClavicleName = "Clavicle.R"; - upperBodyName = "Waist"; - } - - /** - * Load the Sinbad model with an attached sword. - */ - private void loadSinbadWith1Sword() { - cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); - Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); - List list = MySpatial.listSpatials(sword); - for (Spatial spatial : list) { - spatial.setShadowMode(RenderQueue.ShadowMode.Cast); - } - - LinkConfig swordConfig = new LinkConfig(5f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, Vector3f.UNIT_XYZ, - CenterHeuristic.AABB); - dac = new SinbadControl(); - dac.attach("Handle.R", swordConfig, sword); - - animationName = "IdleTop"; - leftClavicleName = "Clavicle.L"; - leftUlnaName = "Ulna.L"; - rightClavicleName = "Clavicle.R"; - upperBodyName = "Waist"; - } - - /** - * Load the Sinbad model with 2 attached swords. - */ - private void loadSinbadWithSwords() { - cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); - Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); - List list = MySpatial.listSpatials(sword); - for (Spatial spatial : list) { - spatial.setShadowMode(RenderQueue.ShadowMode.Cast); - } - - LinkConfig swordConfig = new LinkConfig(5f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, Vector3f.UNIT_XYZ, - CenterHeuristic.AABB); - dac = new SinbadControl(); - dac.attach("Handle.L", swordConfig, sword); - dac.attach("Handle.R", swordConfig, sword); - - animationName = "RunTop"; - leftClavicleName = "Clavicle.L"; - leftUlnaName = "Ulna.L"; - rightClavicleName = "Clavicle.R"; - upperBodyName = "Waist"; - } - - /** - * Update the animation pose and apply it to all bones except the main root - * bone. - */ - private static void matchAnimPose() { - assert animPose != null; - - // Update the animation pose. - Spatial controlledSpatial = sc.getSpatial(); - AnimComposer composer - = controlledSpatial.getControl(AnimComposer.class); - AnimClip clip = composer.getAnimClip(animationName); - double time = composer.getTime(AnimComposer.DEFAULT_LAYER); - animPose.setToClip(clip, time); - - Vector3f acceleration = Vector3f.ZERO; - - // Ensure that all attachment links are dynamic. - for (AttachmentLink link : dac.listLinks(AttachmentLink.class)) { - link.setDynamic(acceleration); - } - Transform tmpTransform = new Transform(); - - // Apply the animation pose to the torso's managed bones. - TorsoLink torsoLink = dac.getTorsoLink(); - int numManagedBones = torsoLink.countManaged(); - for (int mbIndex = 1; mbIndex < numManagedBones; ++mbIndex) { - int boneIndex = torsoLink.boneIndex(mbIndex); - animPose.localTransform(boneIndex, tmpTransform); - torsoLink.setLocalTransform(mbIndex, tmpTransform); - } - - // Apply the animation pose to all bone links and their managed bones. - for (BoneLink boneLink : dac.listLinks(BoneLink.class)) { - int boneIndex = boneLink.boneIndex(0); // the linked bone - Quaternion userRotation = tmpTransform.getRotation(); // alias - animPose.userRotation(boneIndex, userRotation); - boneLink.setDynamic(acceleration, userRotation); - - numManagedBones = boneLink.countManaged(); - for (int mbIndex = 1; mbIndex < numManagedBones; ++mbIndex) { - boneIndex = boneLink.boneIndex(mbIndex); - animPose.localTransform(boneIndex, tmpTransform); - boneLink.setLocalTransform(mbIndex, tmpTransform); - } - } - } - - /** - * Delete all balls from the scene. - */ - private void removeAllBalls() { - List geometries = rootNode.descendantMatches(Geometry.class); - for (Geometry geometry : geometries) { - String name = geometry.getName(); - if (NameGenerator.isFrom(name, "ball")) { - RigidBodyControl rbc - = geometry.getControl(RigidBodyControl.class); - rbc.setPhysicsSpace(null); - geometry.removeControl(rbc); - geometry.removeFromParent(); - } - } - } - - /** - * Save the specified model to a J3O or XML file. - * - * @param model the model to save - * @param assetPath the asset-path portion of the save filename (not null) - */ - private static void save(Spatial model, String assetPath) { - String filePath = filePath(assetPath); - File file = new File(filePath); - - JmeExporter exporter; - if (assetPath.endsWith(".j3o")) { - exporter = BinaryExporter.getInstance(); - } else { - assert assetPath.endsWith(".xml"); - exporter = XMLExporter.getInstance(); - } - - try { - exporter.save(model, file); - } catch (IOException exception) { - System.out.println("Caught IOException: " + exception.getMessage()); - logger.log(Level.SEVERE, - "Output exception while saving {0} to file {1}", - new Object[]{ - MyString.quote(model.getName()), - MyString.quote(filePath) - }); - return; - } - logger.log(Level.INFO, "Saved {0} to file {1}", new Object[]{ - MyString.quote(model.getName()), - MyString.quote(filePath) - }); - } - - /** - * Test re-scaling the model. - * - * @param height the desired height of the model (in world units, >0) - */ - private static void setHeight(float height) { - assert height > 0f : height; - - setCgmHeight(cgModel, height); - centerCgm(cgModel); - dac.rebuild(); - } - - /** - * Toggle mesh rendering on/off. - */ - private void toggleMeshes() { - Spatial.CullHint hint = cgModel.getLocalCullHint(); - if (hint == Spatial.CullHint.Inherit - || hint == Spatial.CullHint.Never) { - hint = Spatial.CullHint.Always; - } else if (hint == Spatial.CullHint.Always) { - hint = Spatial.CullHint.Never; - } - cgModel.setCullHint(hint); - for (Spatial s : rootNode.getChildren()) { - if (s.getName().startsWith("ball")) { - s.setCullHint(hint); - } - } - } - - /** - * Toggle the skeleton visualizer on/off. - */ - private static void toggleSkeleton() { - boolean enabled = sv.isEnabled(); - sv.setEnabled(!enabled); - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - String message = "Test: " + testName + " View: "; - - Spatial.CullHint cull = cgModel.getLocalCullHint(); - message += (cull == Spatial.CullHint.Always) ? "NOmeshes" : "Meshes"; - - boolean debug = bulletAppState.isDebugEnabled(); - if (debug) { - message += "+" + describePhysicsDebugOptions(); - } - message += sv.isEnabled() ? "+Skeleton" : ""; - message += isPaused() ? " PAUSED" : ""; - - double energy = dac.kineticEnergy(); - if (Double.isFinite(energy)) { - message += String.format(" KE=%f", energy); - } - statusText.setText(message); - } -} +/* + Copyright (c) 2018-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.Application; +import com.jme3.app.StatsAppState; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.asset.ModelKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.AttachmentLink; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.bullet.animation.TorsoLink; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.export.JmeExporter; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.xml.XMLExporter; +import com.jme3.font.BitmapText; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InfluenceUtil; +import jme3utilities.MyAsset; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.NameGenerator; +import jme3utilities.debug.SkeletonVisualizer; +import jme3utilities.math.noise.Generator; +import jme3utilities.mesh.Icosphere; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.tunings.BaseMeshControl; +import jme3utilities.minie.test.tunings.Biped; +import jme3utilities.minie.test.tunings.ElephantControl; +import jme3utilities.minie.test.tunings.JaimeControl; +import jme3utilities.minie.test.tunings.MhGameControl; +import jme3utilities.minie.test.tunings.NinjaControl; +import jme3utilities.minie.test.tunings.OtoControl; +import jme3utilities.minie.test.tunings.PuppetControl; +import jme3utilities.minie.test.tunings.SinbadControl; +import jme3utilities.ui.ActionApplication; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.Signals; +import jme3utilities.wes.AnimationEdit; +import jme3utilities.wes.Pose; + +/** + * Test scaling and load/save on a DynamicAnimControl. + *

+ * Seen in the October 2018 demo video: + * https://www.youtube.com/watch?v=A1Rii99nb3Q + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestDac extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * radius of each falling ball (in mesh units) + */ + final private static float ballRadius = 1f; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestDac.class.getName()); + /** + * asset path for saving J3O + */ + final private static String saveAssetPath = "TestDac.j3o"; + /** + * first asset path for saving XML + */ + final private static String saveAssetPath1 = "TestDac_1.xml"; + /** + * 2nd asset path for saving XML + */ + final private static String saveAssetPath2 = "TestDac_2.xml"; + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName = TestDac.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed in the upper-left corner of the GUI node + */ + private static BitmapText statusText; + /** + * important linked bones + */ + private static BoneLink leftClavicle; + private static BoneLink leftFemur; + private static BoneLink leftUlna; + private static BoneLink rightClavicle; + private static BoneLink rightFemur; + private static BoneLink upperBody; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * Control being tested + */ + private static DynamicAnimControl dac; + /** + * Mesh for falling balls + */ + final private static Mesh ballMesh = new Icosphere(2, ballRadius); + /** + * generate names for falling balls + */ + final private static NameGenerator nameGenerator = new NameGenerator(); + /** + * root node of the C-G model on which the Control is being tested + */ + private static Node cgModel; + /** + * animation pose, or null if not in use + */ + private static Pose animPose; + /** + * visualizer for the skeleton of the C-G model + */ + private static SkeletonVisualizer sv; + /** + * SkinningControl of the loaded model + */ + private static SkinningControl sc; + /** + * name of the Animation/Action to play on the C-G model + */ + private static String animationName = null; + /** + * name the important linked bones + */ + private static String leftClavicleName; + private static String leftUlnaName; + private static String rightClavicleName; + private static String upperBodyName; + /** + * name of the test (the model that's loaded) + */ + private static String testName = ""; + /** + * C-G model's local transform when initially loaded + */ + private static Transform resetTransform; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestDac application. + */ + public TestDac() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestDac application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + + Application application = new TestDac(); + application.setSettings(settings); + /* + * Designate a sandbox directory. + * This must be done *prior to* initialization. + */ + try { + ActionApplication.designateSandbox("Written Assets"); + } catch (IOException exception) { + // do nothing + } + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + guiNode.attachChild(statusText); + + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + addLighting(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + float halfExtent = 250f; + float topY = 0f; + attachCubePlatform(halfExtent, topY); + + ColorRGBA ballColor = new ColorRGBA(0.4f, 0f, 0f, 1f); + Material ballMaterial + = MyAsset.createShinyMaterial(assetManager, ballColor); + ballMaterial.setFloat("Shininess", 5f); + registerMaterial("ball", ballMaterial); + + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + registerShape("ball", ballShape); + + addModel("Sinbad"); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + super.configureDumper(); + + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.JointsInSpaces, true); + } + + /** + * Calculate screen bounds for the detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes below the status. + float margin = 10f; // in pixels + float leftX = margin; + float topY = viewPortHeight - 20f - margin; + float width = viewPortWidth - leftX - margin; + float height = topY - margin; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind("add", KeyInput.KEY_INSERT); + dim.bind("amputate left elbow", KeyInput.KEY_DELETE); + dim.bind("blend all to kinematic", KeyInput.KEY_K); + dim.bind(asCollectGarbage, KeyInput.KEY_G); + dim.bind("drop attachments", KeyInput.KEY_PGDN); + dim.bind(asDumpScenes, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bind("freeze all", KeyInput.KEY_F); + dim.bind("freeze upper body", KeyInput.KEY_U); + dim.bind("ghost upper body", KeyInput.KEY_8); + dim.bind("go anim pose", KeyInput.KEY_6); + dim.bind("go bind pose", KeyInput.KEY_B); + dim.bind("go dynamic bind pose", KeyInput.KEY_7); + dim.bind("go floating", KeyInput.KEY_0); + dim.bind("go frozen", KeyInput.KEY_MINUS); + dim.bind("go limp", KeyInput.KEY_SPACE); + dim.bind("limp left arm", KeyInput.KEY_LBRACKET); + dim.bind("limp right arm", KeyInput.KEY_RBRACKET); + dim.bind("load", KeyInput.KEY_L); + + dim.bind("load BaseMesh", KeyInput.KEY_F11); + dim.bind("load Elephant", KeyInput.KEY_F3); + dim.bind("load Jaime", KeyInput.KEY_F2); + dim.bind("load MhGame", KeyInput.KEY_F9); + dim.bind("load Ninja", KeyInput.KEY_F7); + dim.bind("load Oto", KeyInput.KEY_F6); + dim.bind("load Puppet", KeyInput.KEY_F8); + dim.bind("load Sinbad", KeyInput.KEY_F1); + dim.bind("load SinbadWith1Sword", KeyInput.KEY_F10); + dim.bind("load SinbadWithSwords", KeyInput.KEY_F4); + + dim.bind("pin leftFemur", KeyInput.KEY_9); + dim.bind("raise leftFoot", KeyInput.KEY_LCONTROL); + dim.bind("raise leftHand", KeyInput.KEY_LSHIFT); + dim.bind("raise rightFoot", KeyInput.KEY_RCONTROL); + dim.bind("raise rightHand", KeyInput.KEY_RSHIFT); + + dim.bind("reset model transform", KeyInput.KEY_HOME); + dim.bind("save", KeyInput.KEY_COMMA); + dim.bind("set height 1", KeyInput.KEY_1); + dim.bind("set height 2", KeyInput.KEY_2); + dim.bind("set height 3", KeyInput.KEY_3); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("rotateLeft", KeyInput.KEY_LEFT); + dim.bindSignal("rotateRight", KeyInput.KEY_RIGHT); + dim.bindSignal("shower", KeyInput.KEY_I); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleDebug, KeyInput.KEY_SLASH); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind("toggle meshes", KeyInput.KEY_M); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind("toggle skeleton", KeyInput.KEY_V); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "add": + addBall(); + return; + case "amputate left elbow": + dac.amputateSubtree(leftUlna, 2f); + return; + case "blend all to kinematic": + animPose = null; + dac.blendToKinematicMode(2f, null); + return; + case "drop attachments": + dac.dropAttachments(); + return; + + case "freeze all": + animPose = null; + dac.freezeSubtree(dac.getTorsoLink(), false); + return; + case "freeze upper body": + dac.freezeSubtree(upperBody, false); + return; + case "ghost upper body": + dac.setContactResponseSubtree(upperBody, false); + return; + + case "go anim pose": + goAnimPose(); + return; + case "go bind pose": + animPose = null; + dac.bindSubtree(dac.getTorsoLink(), 2f); + return; + case "go dynamic bind pose": + if (dac.isReady()) { + goDynamicBindPose(); + } + return; + case "go floating": + if (dac.isReady()) { + animPose = null; + dac.setDynamicSubtree( + dac.getTorsoLink(), Vector3f.ZERO, false); + } + return; + case "go frozen": + if (dac.isReady()) { + animPose = null; + Vector3f gravity = dac.gravity(null); + dac.setDynamicSubtree( + dac.getTorsoLink(), gravity, true); + } + return; + case "go limp": + if (dac.isReady()) { + animPose = null; + dac.setRagdollMode(); + } + return; + + case "limp left arm": + if (dac.isReady()) { + dac.setDynamicSubtree(leftClavicle, + new Vector3f(0f, -150f, 0f), false); + } + return; + case "limp right arm": + if (dac.isReady()) { + dac.setDynamicSubtree(rightClavicle, + new Vector3f(0f, -150f, 0f), false); + } + return; + + case "load": + load(); + return; + + case "pin leftFemur": + if (dac.isReady()) { + boolean disableForRagdoll = false; + dac.pinToWorld(leftFemur, disableForRagdoll); + } + return; + + case "raise leftFoot": + if (dac.isReady()) { + dac.setDynamicSubtree( + leftFemur, new Vector3f(0f, 100f, 0f), false); + } + return; + case "raise leftHand": + if (dac.isReady()) { + dac.setDynamicSubtree(leftClavicle, + new Vector3f(0f, 100f, 0f), false); + } + return; + case "raise rightFoot": + if (dac.isReady()) { + dac.setDynamicSubtree( + rightFemur, new Vector3f(0f, 100f, 0f), false); + } + return; + case "raise rightHand": + if (dac.isReady()) { + dac.setDynamicSubtree(rightClavicle, + new Vector3f(0f, 100f, 0f), false); + } + return; + + case "reset model transform": + cgModel.setLocalTransform(resetTransform); + return; + case "save": + save(cgModel, saveAssetPath); + save(cgModel, saveAssetPath1); + return; + case "set height 1": + setHeight(5f); + return; + case "set height 2": + setHeight(10f); + return; + case "set height 3": + setHeight(15f); + return; + + case "toggle meshes": + toggleMeshes(); + return; + case "toggle skeleton": + toggleSkeleton(); + return; + + default: + } + String[] words = actionString.split(" "); + if (words.length >= 2 && "load".equals(words[0])) { + addModel(words[1]); + return; + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + statusText.setLocalTranslation(0f, newHeight, 0f); + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + Signals signals = getSignals(); + if (signals.test("shower")) { + addBall(); + } + + float rotateAngle = 0f; + if (signals.test("rotateRight")) { + rotateAngle += tpf; + } + if (signals.test("rotateLeft")) { + rotateAngle -= tpf; + } + if (rotateAngle != 0f) { + rotateAngle /= speed; + Quaternion orientation = MySpatial.worldOrientation(cgModel, null); + Quaternion rotate + = new Quaternion().fromAngles(0f, rotateAngle, 0f); + rotate.mult(orientation, orientation); + MySpatial.setWorldOrientation(cgModel, orientation); + } + + if (animPose != null) { + matchAnimPose(); + } + + updateStatusText(); + } + // ************************************************************************* + // private methods + + /** + * Add a falling ball to the scene. + */ + private void addBall() { + String name = nameGenerator.unique("ball"); + Geometry geometry = new Geometry(name, ballMesh); + rootNode.attachChild(geometry); + + Material material = findMaterial("ball"); + geometry.setMaterial(material); + geometry.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + Generator random = getGenerator(); + Vector3f location = random.nextVector3f(); + location.multLocal(2.5f, 5f, 2.5f); + location.y += 20f; + geometry.move(location); + + Vector3f worldScale = geometry.getWorldScale(); // alias + CollisionShape shape = findShape("ball"); + shape.setScale(worldScale); + + float mass = 12f; + RigidBodyControl rbc = new RigidBodyControl(shape, mass); + rbc.setApplyScale(true); + rbc.setLinearVelocity(new Vector3f(0f, -5f, 0f)); + rbc.setKinematic(false); + rbc.setFriction(10f); + + addCollisionObject(rbc); + rbc.setGravity(new Vector3f(0f, -1.5f, 0f)); + + geometry.addControl(rbc); + } + + /** + * Add lighting and shadows to the main scene. + */ + private void addLighting() { + ColorRGBA ambientColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootNode.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + sun.setName("sun"); + + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.5f); + viewPort.addProcessor(dlsr); + } + + /** + * Add an animated model to the scene, removing any previously added model. + * + * @param modelName the name of the model to add (not null, not empty) + */ + private void addModel(String modelName) { + if (cgModel != null) { + dac.getSpatial().removeControl(dac); + rootNode.detachChild(cgModel); + rootNode.removeControl(sv); + removeAllBalls(); + } + + switch (modelName) { + case "BaseMesh": + loadBaseMesh(); + break; + case "Elephant": + loadElephant(); + break; + case "Jaime": + loadJaime(); + break; + case "MhGame": + loadMhGame(); + break; + case "Ninja": + loadNinja(); + break; + case "Oto": + loadOto(); + break; + case "Puppet": + loadPuppet(); + break; + case "Sinbad": + loadSinbad(); + break; + case "SinbadWith1Sword": + loadSinbadWith1Sword(); + break; + case "SinbadWithSwords": + loadSinbadWithSwords(); + break; + default: + throw new IllegalArgumentException(modelName); + } + + testName = modelName; + animPose = null; + + List list = MySpatial.listSpatials(cgModel); + for (Spatial spatial : list) { + spatial.setShadowMode(RenderQueue.ShadowMode.Cast); + } + cgModel.setCullHint(Spatial.CullHint.Never); + + rootNode.attachChild(cgModel); + setCgmHeight(cgModel, 10f); + centerCgm(cgModel); + resetTransform = cgModel.getLocalTransform().clone(); + + sc = (SkinningControl) RagUtils.findSControl(cgModel); + Spatial controlledSpatial = sc.getSpatial(); + + controlledSpatial.addControl(dac); + dac.setGravity(new Vector3f(0f, -50f, 0f)); + PhysicsSpace physicsSpace = getPhysicsSpace(); + dac.setPhysicsSpace(physicsSpace); + + leftClavicle = dac.findBoneLink(leftClavicleName); + if (dac instanceof Biped) { + BoneLink leftFoot = ((Biped) dac).getLeftFoot(); + leftFemur = leftFoot; + while (leftFemur.getParent() instanceof BoneLink) { + leftFemur = (BoneLink) leftFemur.getParent(); + } + + BoneLink rightFoot = ((Biped) dac).getRightFoot(); + rightFemur = rightFoot; + while (rightFemur.getParent() instanceof BoneLink) { + rightFemur = (BoneLink) rightFemur.getParent(); + } + + } else if (dac instanceof ElephantControl) { + leftFemur = dac.findBoneLink("Oberschenkel_B_L"); + rightFemur = dac.findBoneLink("Oberschenkel_B_R"); + } + + leftUlna = dac.findBoneLink(leftUlnaName); + rightClavicle = dac.findBoneLink(rightClavicleName); + upperBody = dac.findBoneLink(upperBodyName); + + AnimComposer composer + = controlledSpatial.getControl(AnimComposer.class); + composer.setCurrentAction(animationName); + + sv = new SkeletonVisualizer(assetManager, sc); + sv.setLineColor(ColorRGBA.Yellow); + InfluenceUtil.hideNonInfluencers(sv, sc); + rootNode.addControl(sv); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(20f); + flyCam.setZoomSpeed(20f); + + cam.setLocation(new Vector3f(0f, 6f, 25f)); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.setAccuracy(0.01f); // 10-msec timestep + physicsSpace.getSolverInfo().setNumIterations(15); + } + + /** + * Put all physics links into zero-g dynamic mode, fix the torso, and enable + * pose matching. + */ + private static void goAnimPose() { + TorsoLink torsoLink = dac.getTorsoLink(); + torsoLink.setDynamic(Vector3f.ZERO); + boolean disableForRagdoll = true; + dac.fixToWorld(torsoLink, disableForRagdoll); + + animPose = Pose.newInstance(sc); + matchAnimPose(); + } + + /** + * Put all bone/torso links into zero-g dynamic mode and lock all physics + * joints at bind pose. + */ + private static void goDynamicBindPose() { + animPose = null; + + TorsoLink torsoLink = dac.getTorsoLink(); + torsoLink.setDynamic(Vector3f.ZERO); + + for (BoneLink boneLink : dac.listLinks(BoneLink.class)) { + boneLink.setDynamic(Vector3f.ZERO, Quaternion.IDENTITY); + } + } + + /** + * Load the saved model from the J3O file. + */ + private void load() { + ModelKey key = new ModelKey(saveAssetPath); + + // Remove any copy from the asset manager's cache. + assetManager.deleteFromCache(key); + + Spatial loadedScene; + try { + loadedScene = assetManager.loadAsset(key); + } catch (AssetNotFoundException exception) { + logger.log(Level.SEVERE, "Didn''t find asset {0}", + MyString.quote(saveAssetPath)); + return; + } + logger.log(Level.INFO, "Loaded {0} from asset {1}", new Object[]{ + MyString.quote(loadedScene.getName()), + MyString.quote(saveAssetPath) + }); + + save(loadedScene, saveAssetPath2); + } + + /** + * Load the BaseMesh model. + */ + private void loadBaseMesh() { + cgModel = (Node) assetManager.loadModel("Models/BaseMesh/BaseMesh.j3o"); + dac = new BaseMeshControl(); + animationName = "run_01"; + leftClavicleName = "spine2_L.001"; + leftUlnaName = "spine2_L.002"; + rightClavicleName = "spine2_R.001"; + upperBodyName = "spine2"; + /* + * Normalize all quaternions in the animation to resolve some issues + * with the BaseMesh model. + */ + Spatial spatial = RagUtils.findSControl(cgModel).getSpatial(); + AnimComposer composer = spatial.getControl(AnimComposer.class); + AnimClip clip = composer.getAnimClip(animationName); + AnimationEdit.normalizeQuaternions(clip, 0.0001f); + } + + /** + * Load the Elephant model. + */ + private void loadElephant() { + cgModel = (Node) assetManager.loadModel("Models/Elephant/Elephant.j3o"); + cgModel.rotate(0f, 1.6f, 0f); + dac = new ElephantControl(); + animationName = "legUp"; + leftClavicleName = "Oberschenkel_F_L"; + leftUlnaName = "Knee_F_L"; + rightClavicleName = "Oberschenkel_F_R"; + upperBodyName = "joint5"; + } + + /** + * Load the Jaime model. + */ + private void loadJaime() { + cgModel = (Node) assetManager.loadModel("Models/Jaime/Jaime-new.j3o"); + Geometry g = (Geometry) cgModel.getChild(0); + RenderState rs = g.getMaterial().getAdditionalRenderState(); + rs.setFaceCullMode(RenderState.FaceCullMode.Off); + + dac = new JaimeControl(); + animationName = "Punches"; + leftClavicleName = "shoulder.L"; + leftUlnaName = "forearm.L"; + rightClavicleName = "shoulder.R"; + upperBodyName = "ribs"; + } + + /** + * Load the MhGame model. + */ + private void loadMhGame() { + cgModel = (Node) assetManager.loadModel("Models/MhGame/MhGame.j3o"); + dac = new MhGameControl(); + animationName = "expr-lib-pose"; + leftClavicleName = "upperarm_l"; + leftUlnaName = "lowerarm_l"; + rightClavicleName = "upperarm_r"; + upperBodyName = "spine_01"; + } + + /** + * Load the Ninja model. + */ + private void loadNinja() { + cgModel = (Node) assetManager.loadModel("Models/Ninja/Ninja.j3o"); + cgModel.rotate(0f, 3f, 0f); + dac = new NinjaControl(); + animationName = "Walk"; + leftClavicleName = "Joint14"; + leftUlnaName = "Joint16"; + rightClavicleName = "Joint9"; + upperBodyName = "Joint4"; + } + + /** + * Load the Oto model. + */ + private void loadOto() { + cgModel = (Node) assetManager.loadModel("Models/Oto/Oto.j3o"); + dac = new OtoControl(); + animationName = "Walk"; + leftClavicleName = "uparm.left"; + leftUlnaName = "arm.left"; + rightClavicleName = "uparm.right"; + upperBodyName = "spinehigh"; + } + + /** + * Load the Puppet model. + */ + private void loadPuppet() { + cgModel = (Node) assetManager.loadModel("Models/Puppet/Puppet.j3o"); + AnimMigrationUtils.migrate(cgModel); + dac = new PuppetControl(); + animationName = "walk"; + leftClavicleName = "upper_arm.1.L"; + leftUlnaName = "forearm.1.L"; + rightClavicleName = "upper_arm.1.R"; + upperBodyName = "spine"; + } + + /** + * Load the Sinbad model without attachments. + */ + private void loadSinbad() { + cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); + dac = new SinbadControl(); + animationName = "Dance"; + leftClavicleName = "Clavicle.L"; + leftUlnaName = "Ulna.L"; + rightClavicleName = "Clavicle.R"; + upperBodyName = "Waist"; + } + + /** + * Load the Sinbad model with an attached sword. + */ + private void loadSinbadWith1Sword() { + cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); + Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); + List list = MySpatial.listSpatials(sword); + for (Spatial spatial : list) { + spatial.setShadowMode(RenderQueue.ShadowMode.Cast); + } + + LinkConfig swordConfig = new LinkConfig(5f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, Vector3f.UNIT_XYZ, + CenterHeuristic.AABB); + dac = new SinbadControl(); + dac.attach("Handle.R", swordConfig, sword); + + animationName = "IdleTop"; + leftClavicleName = "Clavicle.L"; + leftUlnaName = "Ulna.L"; + rightClavicleName = "Clavicle.R"; + upperBodyName = "Waist"; + } + + /** + * Load the Sinbad model with 2 attached swords. + */ + private void loadSinbadWithSwords() { + cgModel = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.j3o"); + Node sword = (Node) assetManager.loadModel("Models/Sinbad/Sword.j3o"); + List list = MySpatial.listSpatials(sword); + for (Spatial spatial : list) { + spatial.setShadowMode(RenderQueue.ShadowMode.Cast); + } + + LinkConfig swordConfig = new LinkConfig(5f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, Vector3f.UNIT_XYZ, + CenterHeuristic.AABB); + dac = new SinbadControl(); + dac.attach("Handle.L", swordConfig, sword); + dac.attach("Handle.R", swordConfig, sword); + + animationName = "RunTop"; + leftClavicleName = "Clavicle.L"; + leftUlnaName = "Ulna.L"; + rightClavicleName = "Clavicle.R"; + upperBodyName = "Waist"; + } + + /** + * Update the animation pose and apply it to all bones except the main root + * bone. + */ + private static void matchAnimPose() { + assert animPose != null; + + // Update the animation pose. + Spatial controlledSpatial = sc.getSpatial(); + AnimComposer composer + = controlledSpatial.getControl(AnimComposer.class); + AnimClip clip = composer.getAnimClip(animationName); + double time = composer.getTime(AnimComposer.DEFAULT_LAYER); + animPose.setToClip(clip, time); + + Vector3f acceleration = Vector3f.ZERO; + + // Ensure that all attachment links are dynamic. + for (AttachmentLink link : dac.listLinks(AttachmentLink.class)) { + link.setDynamic(acceleration); + } + Transform tmpTransform = new Transform(); + + // Apply the animation pose to the torso's managed bones. + TorsoLink torsoLink = dac.getTorsoLink(); + int numManagedBones = torsoLink.countManaged(); + for (int mbIndex = 1; mbIndex < numManagedBones; ++mbIndex) { + int boneIndex = torsoLink.boneIndex(mbIndex); + animPose.localTransform(boneIndex, tmpTransform); + torsoLink.setLocalTransform(mbIndex, tmpTransform); + } + + // Apply the animation pose to all bone links and their managed bones. + for (BoneLink boneLink : dac.listLinks(BoneLink.class)) { + int boneIndex = boneLink.boneIndex(0); // the linked bone + Quaternion userRotation = tmpTransform.getRotation(); // alias + animPose.userRotation(boneIndex, userRotation); + boneLink.setDynamic(acceleration, userRotation); + + numManagedBones = boneLink.countManaged(); + for (int mbIndex = 1; mbIndex < numManagedBones; ++mbIndex) { + boneIndex = boneLink.boneIndex(mbIndex); + animPose.localTransform(boneIndex, tmpTransform); + boneLink.setLocalTransform(mbIndex, tmpTransform); + } + } + } + + /** + * Delete all balls from the scene. + */ + private void removeAllBalls() { + List geometries = rootNode.descendantMatches(Geometry.class); + for (Geometry geometry : geometries) { + String name = geometry.getName(); + if (NameGenerator.isFrom(name, "ball")) { + RigidBodyControl rbc + = geometry.getControl(RigidBodyControl.class); + rbc.setPhysicsSpace(null); + geometry.removeControl(rbc); + geometry.removeFromParent(); + } + } + } + + /** + * Save the specified model to a J3O or XML file. + * + * @param model the model to save + * @param assetPath the asset-path portion of the save filename (not null) + */ + private static void save(Spatial model, String assetPath) { + String filePath = filePath(assetPath); + File file = new File(filePath); + + JmeExporter exporter; + if (assetPath.endsWith(".j3o")) { + exporter = BinaryExporter.getInstance(); + } else { + assert assetPath.endsWith(".xml"); + exporter = XMLExporter.getInstance(); + } + + try { + exporter.save(model, file); + } catch (IOException exception) { + System.out.println("Caught IOException: " + exception.getMessage()); + logger.log(Level.SEVERE, + "Output exception while saving {0} to file {1}", + new Object[]{ + MyString.quote(model.getName()), + MyString.quote(filePath) + }); + return; + } + logger.log(Level.INFO, "Saved {0} to file {1}", new Object[]{ + MyString.quote(model.getName()), + MyString.quote(filePath) + }); + } + + /** + * Test re-scaling the model. + * + * @param height the desired height of the model (in world units, >0) + */ + private static void setHeight(float height) { + assert height > 0f : height; + + setCgmHeight(cgModel, height); + centerCgm(cgModel); + dac.rebuild(); + } + + /** + * Toggle mesh rendering on/off. + */ + private void toggleMeshes() { + Spatial.CullHint hint = cgModel.getLocalCullHint(); + if (hint == Spatial.CullHint.Inherit + || hint == Spatial.CullHint.Never) { + hint = Spatial.CullHint.Always; + } else if (hint == Spatial.CullHint.Always) { + hint = Spatial.CullHint.Never; + } + cgModel.setCullHint(hint); + for (Spatial s : rootNode.getChildren()) { + if (s.getName().startsWith("ball")) { + s.setCullHint(hint); + } + } + } + + /** + * Toggle the skeleton visualizer on/off. + */ + private static void toggleSkeleton() { + boolean enabled = sv.isEnabled(); + sv.setEnabled(!enabled); + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + String message = "Test: " + testName + " View: "; + + Spatial.CullHint cull = cgModel.getLocalCullHint(); + message += (cull == Spatial.CullHint.Always) ? "NOmeshes" : "Meshes"; + + boolean debug = bulletAppState.isDebugEnabled(); + if (debug) { + message += "+" + describePhysicsDebugOptions(); + } + message += sv.isEnabled() ? "+Skeleton" : ""; + message += isPaused() ? " PAUSED" : ""; + + double energy = dac.kineticEnergy(); + if (Double.isFinite(energy)) { + message += String.format(" KE=%f", energy); + } + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TestGearJoint.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TestGearJoint.java index 6052644a5..d95d77fb4 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TestGearJoint.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TestGearJoint.java @@ -1,398 +1,398 @@ -/* - Copyright (c) 2022-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.StatsAppState; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.joints.GearJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.font.BitmapText; -import com.jme3.input.KeyInput; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import jme3utilities.math.RectSizeLimits; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.DisplaySettings; -import jme3utilities.ui.DsEditOverlay; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.ShowDialog; -import jme3utilities.ui.Signals; - -/** - * Test/demonstrate gear joints. - *

- * Collision objects are rendered entirely by debug visualization. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestGearJoint - extends PhysicsDemo - implements PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestGearJoint.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = TestGearJoint.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed at the bottom of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * proposed display settings (for editing) - */ - private static DisplaySettings proposedSettings; - /** - * subject body to which torques are applied - */ - private static PhysicsRigidBody driveshaft; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestGearJoint application. - */ - public TestGearJoint() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestGearJoint application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Process any command-line arguments. - ShowDialog showDialog = ShowDialog.Never; - for (String arg : arguments) { - switch (arg) { - case "--deleteOnly": - Heart.deleteStoredSettings(applicationName); - return; - - case "--showSettingsDialog": - showDialog = ShowDialog.FirstTime; - break; - - case "--verbose": - Heart.setLoggingLevels(Level.INFO); - break; - - default: - logger.log(Level.WARNING, - "Ignored unknown command-line argument {0}", - MyString.quote(arg)); - } - } - mainStartup(showDialog, title); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - DsEditOverlay dseOverlay = new DsEditOverlay(proposedSettings); - dseOverlay.setBackgroundColor(new ColorRGBA(0.05f, 0f, 0f, 1f)); - boolean success = stateManager.attach(dseOverlay); - assert success; - super.acorusInit(); - - // Hide the render-statistics overlay. - stateManager.getState(StatsAppState.class).toggleStats(); - - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - statusText.setLocalTranslation(205f, 25f, 0f); - guiNode.attachChild(statusText); - - configureCamera(); - configureDumper(); - configurePhysics(); - - ColorRGBA bgColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); - viewPort.setBackgroundColor(bgColor); - - float length = 0.8f; - attachWorldAxes(length); - - // Add an elongated dynamic body for the driveshaft. - float radius = 0.5f; - float height = 3f; - CollisionShape driveshaftShape = new CylinderCollisionShape( - radius, height, PhysicsSpace.AXIS_Y); - driveshaft = new PhysicsRigidBody(driveshaftShape); - driveshaft.setPhysicsLocation(new Vector3f(-1f, 0.2f, 0f)); - driveshaft.setEnableSleep(false); - addCollisionObject(driveshaft); - - // Add a flattened dynamic body for the wheel. - radius = 2f; - height = 0.5f; - CollisionShape wheelShape = new CylinderCollisionShape( - radius, height, PhysicsSpace.AXIS_X); - float wheelMass = 2f; - PhysicsRigidBody wheel = new PhysicsRigidBody(wheelShape, wheelMass); - wheel.setPhysicsLocation(new Vector3f(1f, 0.2f, 0f)); - wheel.setEnableSleep(false); - addCollisionObject(wheel); - /* - * Join them with a GearJoint. - * The driveshaft makes 3 revolutions for each revolution of the wheel. - */ - GearJoint gear = new GearJoint( - driveshaft, wheel, Vector3f.UNIT_Y, Vector3f.UNIT_X, 3f); - addJoint(gear); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of debug axis arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 2f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpScenes, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - dim.bindSignal("+torque", KeyInput.KEY_F, KeyInput.KEY_DOWN); - dim.bindSignal("-torque", KeyInput.KEY_R, KeyInput.KEY_UP); - - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleWorldAxes, KeyInput.KEY_SPACE); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case asEditDisplaySettings: - activateInputMode("dsEdit"); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusText(); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before the physics is stepped. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Check UI signals and apply forces/torques accordingly. - Signals signals = getSignals(); - - if (signals.test("+torque")) { - driveshaft.applyTorque(new Vector3f(0f, 1f, 0f)); - } - if (signals.test("-torque")) { - driveshaft.applyTorque(new Vector3f(0f, -1f, 0f)); - } - } - - /** - * Callback from Bullet, invoked just after the physics has been stepped. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(4f); - flyCam.setZoomSpeed(4f); - - cam.setLocation(new Vector3f(2.2f, 2f, 7.7f)); - cam.setRotation(new Quaternion(-0.007f, 0.984838f, -0.12f, -0.1251f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - // Set up Bullet physics and create a physics space. - bulletAppState = new BulletAppState(); - float axisLength = maxArrowLength(); - bulletAppState.setDebugAxisLength(axisLength); - bulletAppState.setDebugEnabled(true); - stateManager.attach(bulletAppState); - - PhysicsSpace space = getPhysicsSpace(); - space.addTickListener(this); - setGravityAll(0f); - } - - /** - * Initialization performed immediately after parsing the command-line - * arguments. - * - * @param showDialog when to show the JME settings dialog (not null) - * @param title for the title bar of the app's window - */ - private static void mainStartup( - final ShowDialog showDialog, final String title) { - TestGearJoint application = new TestGearJoint(); - - RectSizeLimits sizeLimits = new RectSizeLimits( - 530, 480, // min width, height - 2_048, 1_080 // max width, height - ); - proposedSettings = new DisplaySettings( - application, applicationName, sizeLimits) { - @Override - protected void applyOverrides(AppSettings settings) { - setShowDialog(showDialog); - settings.setAudioRenderer(null); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - if (settings.getSamples() < 1) { - settings.setSamples(4); // anti-aliasing - } - settings.setResizable(true); - settings.setTitle(title); // Customize the window's title bar. - } - }; - AppSettings appSettings = proposedSettings.initialize(); - if (appSettings == null) { - return; - } - - application.setSettings(appSettings); - /* - * If the settings dialog should be shown, - * it has already been shown by DisplaySettings.initialize(). - */ - application.setShowSettings(false); - application.start(); - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - String message = isPaused() ? "PAUSED" : ""; - statusText.setText(message); - } -} +/* + Copyright (c) 2022-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.StatsAppState; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.joints.GearJoint; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import jme3utilities.math.RectSizeLimits; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.DisplaySettings; +import jme3utilities.ui.DsEditOverlay; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.ShowDialog; +import jme3utilities.ui.Signals; + +/** + * Test/demonstrate gear joints. + *

+ * Collision objects are rendered entirely by debug visualization. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestGearJoint + extends PhysicsDemo + implements PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestGearJoint.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = TestGearJoint.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed at the bottom of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * proposed display settings (for editing) + */ + private static DisplaySettings proposedSettings; + /** + * subject body to which torques are applied + */ + private static PhysicsRigidBody driveshaft; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestGearJoint application. + */ + public TestGearJoint() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestGearJoint application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Process any command-line arguments. + ShowDialog showDialog = ShowDialog.Never; + for (String arg : arguments) { + switch (arg) { + case "--deleteOnly": + Heart.deleteStoredSettings(applicationName); + return; + + case "--showSettingsDialog": + showDialog = ShowDialog.FirstTime; + break; + + case "--verbose": + Heart.setLoggingLevels(Level.INFO); + break; + + default: + logger.log(Level.WARNING, + "Ignored unknown command-line argument {0}", + MyString.quote(arg)); + } + } + mainStartup(showDialog, title); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + DsEditOverlay dseOverlay = new DsEditOverlay(proposedSettings); + dseOverlay.setBackgroundColor(new ColorRGBA(0.05f, 0f, 0f, 1f)); + boolean success = stateManager.attach(dseOverlay); + assert success; + super.acorusInit(); + + // Hide the render-statistics overlay. + stateManager.getState(StatsAppState.class).toggleStats(); + + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + statusText.setLocalTranslation(205f, 25f, 0f); + guiNode.attachChild(statusText); + + configureCamera(); + configureDumper(); + configurePhysics(); + + ColorRGBA bgColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + viewPort.setBackgroundColor(bgColor); + + float length = 0.8f; + attachWorldAxes(length); + + // Add an elongated dynamic body for the driveshaft. + float radius = 0.5f; + float height = 3f; + CollisionShape driveshaftShape = new CylinderCollisionShape( + radius, height, PhysicsSpace.AXIS_Y); + driveshaft = new PhysicsRigidBody(driveshaftShape); + driveshaft.setPhysicsLocation(new Vector3f(-1f, 0.2f, 0f)); + driveshaft.setEnableSleep(false); + addCollisionObject(driveshaft); + + // Add a flattened dynamic body for the wheel. + radius = 2f; + height = 0.5f; + CollisionShape wheelShape = new CylinderCollisionShape( + radius, height, PhysicsSpace.AXIS_X); + float wheelMass = 2f; + PhysicsRigidBody wheel = new PhysicsRigidBody(wheelShape, wheelMass); + wheel.setPhysicsLocation(new Vector3f(1f, 0.2f, 0f)); + wheel.setEnableSleep(false); + addCollisionObject(wheel); + /* + * Join them with a GearJoint. + * The driveshaft makes 3 revolutions for each revolution of the wheel. + */ + GearJoint gear = new GearJoint( + driveshaft, wheel, Vector3f.UNIT_Y, Vector3f.UNIT_X, 3f); + addJoint(gear); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of debug axis arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 2f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpScenes, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + dim.bindSignal("+torque", KeyInput.KEY_F, KeyInput.KEY_DOWN); + dim.bindSignal("-torque", KeyInput.KEY_R, KeyInput.KEY_UP); + + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleWorldAxes, KeyInput.KEY_SPACE); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case asEditDisplaySettings: + activateInputMode("dsEdit"); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusText(); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before the physics is stepped. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Check UI signals and apply forces/torques accordingly. + Signals signals = getSignals(); + + if (signals.test("+torque")) { + driveshaft.applyTorque(new Vector3f(0f, 1f, 0f)); + } + if (signals.test("-torque")) { + driveshaft.applyTorque(new Vector3f(0f, -1f, 0f)); + } + } + + /** + * Callback from Bullet, invoked just after the physics has been stepped. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(4f); + flyCam.setZoomSpeed(4f); + + cam.setLocation(new Vector3f(2.2f, 2f, 7.7f)); + cam.setRotation(new Quaternion(-0.007f, 0.984838f, -0.12f, -0.1251f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + // Set up Bullet physics and create a physics space. + bulletAppState = new BulletAppState(); + float axisLength = maxArrowLength(); + bulletAppState.setDebugAxisLength(axisLength); + bulletAppState.setDebugEnabled(true); + stateManager.attach(bulletAppState); + + PhysicsSpace space = getPhysicsSpace(); + space.addTickListener(this); + setGravityAll(0f); + } + + /** + * Initialization performed immediately after parsing the command-line + * arguments. + * + * @param showDialog when to show the JME settings dialog (not null) + * @param title for the title bar of the app's window + */ + private static void mainStartup( + final ShowDialog showDialog, final String title) { + TestGearJoint application = new TestGearJoint(); + + RectSizeLimits sizeLimits = new RectSizeLimits( + 530, 480, // min width, height + 2_048, 1_080 // max width, height + ); + proposedSettings = new DisplaySettings( + application, applicationName, sizeLimits) { + @Override + protected void applyOverrides(AppSettings settings) { + setShowDialog(showDialog); + settings.setAudioRenderer(null); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + if (settings.getSamples() < 1) { + settings.setSamples(4); // anti-aliasing + } + settings.setResizable(true); + settings.setTitle(title); // Customize the window's title bar. + } + }; + AppSettings appSettings = proposedSettings.initialize(); + if (appSettings == null) { + return; + } + + application.setSettings(appSettings); + /* + * If the settings dialog should be shown, + * it has already been shown by DisplaySettings.initialize(). + */ + application.setShowSettings(false); + application.start(); + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + String message = isPaused() ? "PAUSED" : ""; + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TestMultiBody.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TestMultiBody.java index b97934f54..c45313859 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TestMultiBody.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TestMultiBody.java @@ -1,400 +1,400 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.MultiBody; -import com.jme3.bullet.MultiBodyAppState; -import com.jme3.bullet.MultiBodyLink; -import com.jme3.bullet.MultiBodySpace; -import com.jme3.bullet.SolverType; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.font.BitmapText; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.system.AppSettings; -import com.jme3.util.BufferUtils; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; - -/** - * Demo/testbed for multi-body physics. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestMultiBody extends PhysicsDemo { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestMultiBody.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = TestMultiBody.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * lines of text displayed in the upper-left corner of the GUI node ([0] is - * the top line) - */ - final private static BitmapText[] statusLines = new BitmapText[1]; - /** - * AppState to manage the PhysicsSpace - */ - private static MultiBodyAppState bulletAppState; - /** - * name of the test being run - */ - private static String testName = "test1"; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestMultiBody application. - */ - public TestMultiBody() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestMultiBody application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Enable direct-memory tracking. - BufferUtils.setTrackDirectMemoryEnabled(true); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(16); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - settings.setVSync(false); - - Application application = new TestMultiBody(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - addStatusLines(); - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - addLighting(rootNode, false); - this.speed = pausedSpeed; - - float halfExtent = 4f; - float topY = -1f; - attachCubePlatform(halfExtent, topY); - - addMultiBody(); - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - ColorRGBA gray = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); - Material platform = MyAsset.createUnshadedMaterial(assetManager, gray); - registerMaterial("platform", platform); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 0.5f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpScenes, KeyInput.KEY_P); - dim.bind(asDumpSpace, KeyInput.KEY_O); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind("test test1", KeyInput.KEY_F1); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case "test test1": - testName = "test1"; - cleanupAfterTest(); - - float halfExtent = 4f; - float topY = -1f; - attachCubePlatform(halfExtent, topY); - - addMultiBody(); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusLines(); - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - * @param shadowFlag if true, add a shadow renderer to the default viewport - */ - private void addLighting(Spatial rootSpatial, boolean shadowFlag) { - ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); - Vector3f direction = new Vector3f(1f, -2f, -2f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - rootSpatial.addLight(sun); - sun.setName("sun"); - - rootSpatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - if (shadowFlag) { - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.5f); - viewPort.addProcessor(dlsr); - } - } - - /** - * Add a MultiBody to the scene. - */ - private void addMultiBody() { - int numLinks = 1; - float linkMass = 1f; - Vector3f inertia = new Vector3f(1f, 1f, 1f); - boolean fixedBase = false; - boolean canSleep = false; - MultiBody multiBody = new MultiBody( - numLinks, linkMass, inertia, fixedBase, canSleep); - - CollisionShape baseShape = new SphereCollisionShape(0.3f); - multiBody.addBaseCollider(baseShape); - - MultiBodyLink parent = null; - boolean disableCollision = false; - Vector3f offset = new Vector3f(0f, -0.1f, 1f); - MultiBodyLink link = multiBody.configureSphericalLink(linkMass, inertia, - parent, Quaternion.IDENTITY, offset, offset, disableCollision); - CollisionShape linkShape = new BoxCollisionShape(0.3f); - link.addCollider(linkShape); - - MultiBodySpace space = (MultiBodySpace) getPhysicsSpace(); - space.addMultiBody(multiBody); - } - - /** - * Add status lines to the GUI. - */ - private void addStatusLines() { - for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - guiNode.attachChild(statusLines[lineIndex]); - } - } - - /** - * Clean up after a test. - */ - private void cleanupAfterTest() { - // Remove any scenery. Debug meshes are under a different root node. - rootNode.detachAllChildren(); - - stateManager.detach(bulletAppState); - configurePhysics(); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.02f; - float far = 20f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(2f); - flyCam.setZoomSpeed(2f); - - cam.setLocation(new Vector3f(3.778f, 2.2f, 0.971f)); - cam.setRotation(new Quaternion(0.2181f, -0.68631f, 0.2285f, 0.65513f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new MultiBodyAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setSolverType(SolverType.Lemke); - float axisLength = maxArrowLength(); - bulletAppState.setDebugAxisLength(axisLength); - stateManager.attach(bulletAppState); - - MultiBodySpace physicsSpace = bulletAppState.getMultiBodySpace(); - physicsSpace.getSolverInfo().setGlobalCfm(0.1f); // for the Lemke solver - setGravityAll(2f); - } - - /** - * Update the status lines in the GUI. - */ - private void updateStatusLines() { - String message = String.format( - "Test: %s%s", testName, isPaused() ? " PAUSED" : ""); - statusLines[0].setText(message); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.MultiBody; +import com.jme3.bullet.MultiBodyAppState; +import com.jme3.bullet.MultiBodyLink; +import com.jme3.bullet.MultiBodySpace; +import com.jme3.bullet.SolverType; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.font.BitmapText; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import com.jme3.util.BufferUtils; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; + +/** + * Demo/testbed for multi-body physics. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestMultiBody extends PhysicsDemo { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestMultiBody.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = TestMultiBody.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * lines of text displayed in the upper-left corner of the GUI node ([0] is + * the top line) + */ + final private static BitmapText[] statusLines = new BitmapText[1]; + /** + * AppState to manage the PhysicsSpace + */ + private static MultiBodyAppState bulletAppState; + /** + * name of the test being run + */ + private static String testName = "test1"; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestMultiBody application. + */ + public TestMultiBody() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestMultiBody application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Enable direct-memory tracking. + BufferUtils.setTrackDirectMemoryEnabled(true); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(16); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + settings.setVSync(false); + + Application application = new TestMultiBody(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + addStatusLines(); + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + addLighting(rootNode, false); + this.speed = pausedSpeed; + + float halfExtent = 4f; + float topY = -1f; + attachCubePlatform(halfExtent, topY); + + addMultiBody(); + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + ColorRGBA gray = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + Material platform = MyAsset.createUnshadedMaterial(assetManager, gray); + registerMaterial("platform", platform); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 0.5f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpScenes, KeyInput.KEY_P); + dim.bind(asDumpSpace, KeyInput.KEY_O); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind("test test1", KeyInput.KEY_F1); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case "test test1": + testName = "test1"; + cleanupAfterTest(); + + float halfExtent = 4f; + float topY = -1f; + attachCubePlatform(halfExtent, topY); + + addMultiBody(); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusLines(); + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + * @param shadowFlag if true, add a shadow renderer to the default viewport + */ + private void addLighting(Spatial rootSpatial, boolean shadowFlag) { + ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); + Vector3f direction = new Vector3f(1f, -2f, -2f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + rootSpatial.addLight(sun); + sun.setName("sun"); + + rootSpatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + if (shadowFlag) { + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.5f); + viewPort.addProcessor(dlsr); + } + } + + /** + * Add a MultiBody to the scene. + */ + private void addMultiBody() { + int numLinks = 1; + float linkMass = 1f; + Vector3f inertia = new Vector3f(1f, 1f, 1f); + boolean fixedBase = false; + boolean canSleep = false; + MultiBody multiBody = new MultiBody( + numLinks, linkMass, inertia, fixedBase, canSleep); + + CollisionShape baseShape = new SphereCollisionShape(0.3f); + multiBody.addBaseCollider(baseShape); + + MultiBodyLink parent = null; + boolean disableCollision = false; + Vector3f offset = new Vector3f(0f, -0.1f, 1f); + MultiBodyLink link = multiBody.configureSphericalLink(linkMass, inertia, + parent, Quaternion.IDENTITY, offset, offset, disableCollision); + CollisionShape linkShape = new BoxCollisionShape(0.3f); + link.addCollider(linkShape); + + MultiBodySpace space = (MultiBodySpace) getPhysicsSpace(); + space.addMultiBody(multiBody); + } + + /** + * Add status lines to the GUI. + */ + private void addStatusLines() { + for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + guiNode.attachChild(statusLines[lineIndex]); + } + } + + /** + * Clean up after a test. + */ + private void cleanupAfterTest() { + // Remove any scenery. Debug meshes are under a different root node. + rootNode.detachAllChildren(); + + stateManager.detach(bulletAppState); + configurePhysics(); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.02f; + float far = 20f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(2f); + flyCam.setZoomSpeed(2f); + + cam.setLocation(new Vector3f(3.778f, 2.2f, 0.971f)); + cam.setRotation(new Quaternion(0.2181f, -0.68631f, 0.2285f, 0.65513f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new MultiBodyAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setSolverType(SolverType.Lemke); + float axisLength = maxArrowLength(); + bulletAppState.setDebugAxisLength(axisLength); + stateManager.attach(bulletAppState); + + MultiBodySpace physicsSpace = bulletAppState.getMultiBodySpace(); + physicsSpace.getSolverInfo().setGlobalCfm(0.1f); // for the Lemke solver + setGravityAll(2f); + } + + /** + * Update the status lines in the GUI. + */ + private void updateStatusLines() { + String message = String.format( + "Test: %s%s", testName, isPaused() ? " PAUSED" : ""); + statusLines[0].setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TestPin.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TestPin.java index 4c6b6e674..e2b81b0af 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TestPin.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TestPin.java @@ -1,214 +1,214 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.collision.shapes.infos.IndexedMesh; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.math.Vector3f; -import com.jme3.system.AppSettings; -import jme3utilities.MyMesh; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.AcorusDemo; - -/** - * A simple cloth simulation with a pinned node, using a native mesh. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestPin extends AcorusDemo { - // ************************************************************************* - // constants and loggers - - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName = TestPin.class.getSimpleName(); - // ************************************************************************* - // constructors - - /** - * Instantiate the TestPin application. - */ - public TestPin() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestPin application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setTitle(title); // Customize the window's title bar. - - TestPin application = new TestPin(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // AcorusDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - super.acorusInit(); - - // Set up Bullet physics (with debug enabled). - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - bulletAppState.setDebugEnabled(true); - stateManager.attach(bulletAppState); - PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - // Relocate the camera. - cam.setLocation(new Vector3f(0f, 1f, 8f)); - - // Create a static, rigid sphere and add it to the physics space. - float radius = 1f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - PhysicsRigidBody sphere - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(sphere); - - // Generate a subdivided square mesh with alternating diagonals. - int numLines = 41; - float lineSpacing = 0.1f; // mesh units - IndexedMesh squareGrid - = createClothGrid(numLines, numLines, lineSpacing); - - // Create a soft square and add it to the physics space. - PhysicsSoftBody cloth = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromNativeMesh(squareGrid, cloth); - physicsSpace.addCollisionObject(cloth); - - // Pin one of the corner nodes by setting its mass to zero. - int nodeIndex = 0; - cloth.setNodeMass(nodeIndex, PhysicsBody.massForStatic); - - // Make the cloth flexible by altering the angular stiffness - // of its material. - SoftBodyMaterial mat = cloth.getSoftMaterial(); - mat.setAngularStiffness(0f); // default=1 - - // Improve simulation accuracy by increasing - // the number of position-solver iterations for the cloth. - SoftBodyConfig config = cloth.getSoftConfig(); - config.setPositionIterations(9); // default=1 - - // Translate the cloth upward to its starting location. - cloth.applyTranslation(new Vector3f(0f, 2f, 0f)); - } - // ************************************************************************* - // private methods - - /** - * Instantiate a grid in the X-Z plane, centered on (0,0,0). - * - * @param xLines the desired number of grid lines parallel to the X axis - * (≥2) - * @param zLines the desired number of grid lines parallel to the Z axis - * (≥2) - * @param lineSpacing the desired initial distance between adjacent grid - * lines (in mesh units, >0) - * @return a new IndexedMesh - */ - private static IndexedMesh - createClothGrid(int xLines, int zLines, float lineSpacing) { - Validate.inRange(xLines, "X lines", 2, Integer.MAX_VALUE); - Validate.inRange(zLines, "Z lines", 2, Integer.MAX_VALUE); - Validate.positive(lineSpacing, "line spacing"); - - int numVertices = xLines * zLines; - Vector3f[] positionArray = new Vector3f[numVertices]; - - // Write the vertex locations: - int vectorIndex = 0; - for (int xIndex = 0; xIndex < zLines; ++xIndex) { - float x = (2 * xIndex - zLines + 1) * lineSpacing / 2f; - for (int zIndex = 0; zIndex < xLines; ++zIndex) { - float z = (2 * zIndex - xLines + 1) * lineSpacing / 2f; - positionArray[vectorIndex] = new Vector3f(x, 0f, z); - ++vectorIndex; - } - } - assert vectorIndex == positionArray.length; - - int numTriangles = 2 * (xLines - 1) * (zLines - 1); - int numIndices = MyMesh.vpt * numTriangles; - int[] indexArray = new int[numIndices]; - - // Write vertex indices for triangles: - int intIndex = 0; - for (int zIndex = 0; zIndex < xLines - 1; ++zIndex) { - for (int xIndex = 0; xIndex < zLines - 1; ++xIndex) { - // 4 vertices and 2 triangles forming a square - int vi0 = zIndex + xLines * xIndex; - int vi1 = vi0 + 1; - int vi2 = vi0 + xLines; - int vi3 = vi1 + xLines; - if ((xIndex + zIndex) % 2 == 0) { - // major diagonal: joins vi1 to vi2 - indexArray[intIndex] = vi0; - indexArray[intIndex + 1] = vi1; - indexArray[intIndex + 2] = vi2; - - indexArray[intIndex + 3] = vi3; - indexArray[intIndex + 4] = vi2; - indexArray[intIndex + 5] = vi1; - } else { - // minor diagonal: joins vi0 to vi3 - indexArray[intIndex] = vi0; - indexArray[intIndex + 1] = vi1; - indexArray[intIndex + 2] = vi3; - - indexArray[intIndex + 3] = vi3; - indexArray[intIndex + 4] = vi2; - indexArray[intIndex + 5] = vi0; - } - intIndex += 6; - } - } - assert intIndex == indexArray.length; - - IndexedMesh result = new IndexedMesh(positionArray, indexArray); - return result; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.collision.shapes.infos.IndexedMesh; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.math.Vector3f; +import com.jme3.system.AppSettings; +import jme3utilities.MyMesh; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.AcorusDemo; + +/** + * A simple cloth simulation with a pinned node, using a native mesh. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestPin extends AcorusDemo { + // ************************************************************************* + // constants and loggers + + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName = TestPin.class.getSimpleName(); + // ************************************************************************* + // constructors + + /** + * Instantiate the TestPin application. + */ + public TestPin() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestPin application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setTitle(title); // Customize the window's title bar. + + TestPin application = new TestPin(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // AcorusDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + super.acorusInit(); + + // Set up Bullet physics (with debug enabled). + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + bulletAppState.setDebugEnabled(true); + stateManager.attach(bulletAppState); + PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + // Relocate the camera. + cam.setLocation(new Vector3f(0f, 1f, 8f)); + + // Create a static, rigid sphere and add it to the physics space. + float radius = 1f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + PhysicsRigidBody sphere + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(sphere); + + // Generate a subdivided square mesh with alternating diagonals. + int numLines = 41; + float lineSpacing = 0.1f; // mesh units + IndexedMesh squareGrid + = createClothGrid(numLines, numLines, lineSpacing); + + // Create a soft square and add it to the physics space. + PhysicsSoftBody cloth = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromNativeMesh(squareGrid, cloth); + physicsSpace.addCollisionObject(cloth); + + // Pin one of the corner nodes by setting its mass to zero. + int nodeIndex = 0; + cloth.setNodeMass(nodeIndex, PhysicsBody.massForStatic); + + // Make the cloth flexible by altering the angular stiffness + // of its material. + SoftBodyMaterial mat = cloth.getSoftMaterial(); + mat.setAngularStiffness(0f); // default=1 + + // Improve simulation accuracy by increasing + // the number of position-solver iterations for the cloth. + SoftBodyConfig config = cloth.getSoftConfig(); + config.setPositionIterations(9); // default=1 + + // Translate the cloth upward to its starting location. + cloth.applyTranslation(new Vector3f(0f, 2f, 0f)); + } + // ************************************************************************* + // private methods + + /** + * Instantiate a grid in the X-Z plane, centered on (0,0,0). + * + * @param xLines the desired number of grid lines parallel to the X axis + * (≥2) + * @param zLines the desired number of grid lines parallel to the Z axis + * (≥2) + * @param lineSpacing the desired initial distance between adjacent grid + * lines (in mesh units, >0) + * @return a new IndexedMesh + */ + private static IndexedMesh + createClothGrid(int xLines, int zLines, float lineSpacing) { + Validate.inRange(xLines, "X lines", 2, Integer.MAX_VALUE); + Validate.inRange(zLines, "Z lines", 2, Integer.MAX_VALUE); + Validate.positive(lineSpacing, "line spacing"); + + int numVertices = xLines * zLines; + Vector3f[] positionArray = new Vector3f[numVertices]; + + // Write the vertex locations: + int vectorIndex = 0; + for (int xIndex = 0; xIndex < zLines; ++xIndex) { + float x = (2 * xIndex - zLines + 1) * lineSpacing / 2f; + for (int zIndex = 0; zIndex < xLines; ++zIndex) { + float z = (2 * zIndex - xLines + 1) * lineSpacing / 2f; + positionArray[vectorIndex] = new Vector3f(x, 0f, z); + ++vectorIndex; + } + } + assert vectorIndex == positionArray.length; + + int numTriangles = 2 * (xLines - 1) * (zLines - 1); + int numIndices = MyMesh.vpt * numTriangles; + int[] indexArray = new int[numIndices]; + + // Write vertex indices for triangles: + int intIndex = 0; + for (int zIndex = 0; zIndex < xLines - 1; ++zIndex) { + for (int xIndex = 0; xIndex < zLines - 1; ++xIndex) { + // 4 vertices and 2 triangles forming a square + int vi0 = zIndex + xLines * xIndex; + int vi1 = vi0 + 1; + int vi2 = vi0 + xLines; + int vi3 = vi1 + xLines; + if ((xIndex + zIndex) % 2 == 0) { + // major diagonal: joins vi1 to vi2 + indexArray[intIndex] = vi0; + indexArray[intIndex + 1] = vi1; + indexArray[intIndex + 2] = vi2; + + indexArray[intIndex + 3] = vi3; + indexArray[intIndex + 4] = vi2; + indexArray[intIndex + 5] = vi1; + } else { + // minor diagonal: joins vi0 to vi3 + indexArray[intIndex] = vi0; + indexArray[intIndex + 1] = vi1; + indexArray[intIndex + 2] = vi3; + + indexArray[intIndex + 3] = vi3; + indexArray[intIndex + 4] = vi2; + indexArray[intIndex + 5] = vi0; + } + intIndex += 6; + } + } + assert intIndex == indexArray.length; + + IndexedMesh result = new IndexedMesh(positionArray, indexArray); + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java index dc56aea80..bb0a2e137 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java @@ -1,1029 +1,1029 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.anim.AnimComposer; -import com.jme3.anim.SkinningControl; -import com.jme3.anim.tween.action.Action; -import com.jme3.anim.util.AnimMigrationUtils; -import com.jme3.app.Application; -import com.jme3.app.state.AppState; -import com.jme3.bounding.BoundingBox; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.animation.RagUtils; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.debug.DebugMeshInitListener; -import com.jme3.bullet.joints.Anchor; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.Aero; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.font.BitmapText; -import com.jme3.font.Rectangle; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.system.AppSettings; -import com.jme3.texture.Texture; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.math.MyArray; -import jme3utilities.math.MyVector3f; -import jme3utilities.mesh.ClothGrid; -import jme3utilities.mesh.Icosphere; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.FilterAll; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.minie.test.tunings.PuppetControl; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; - -/** - * Test/demonstrate soft-body physics. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestSoftBody - extends PhysicsDemo - implements DebugInitListener { - // ************************************************************************* - // constants and loggers - - /** - * number of axes in a vector - */ - final private static int numAxes = 3; - /** - * number of lines of text in the overlay - */ - final private static int numStatusLines = 2; - /** - * add UVs to the debug mesh of a flag - */ - final private static DebugMeshInitListener flagDmiListener - = new DebugMeshInitListener() { - @Override - public void debugMeshInit(Mesh debugMesh) { - FloatBuffer positions - = debugMesh.getFloatBuffer(VertexBuffer.Type.Position); - int numVertices = positions.limit() / numAxes; - FloatBuffer uvs = BufferUtils.createFloatBuffer(2 * numVertices); - debugMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); - for (int vertexI = 0; vertexI < numVertices; ++vertexI) { - float x = positions.get(numAxes * vertexI); - float y = positions.get(numAxes * vertexI + 1); - float u = x - 0.5f; - float v = 2f - y; - uvs.put(u).put(v); - } - uvs.flip(); - } - }; - /** - * add UVs to the debug mesh of a tablecloth - */ - final private static DebugMeshInitListener tableclothDmiListener - = new DebugMeshInitListener() { - @Override - public void debugMeshInit(Mesh debugMesh) { - FloatBuffer positions - = debugMesh.getFloatBuffer(VertexBuffer.Type.Position); - int numVertices = positions.limit() / numAxes; - FloatBuffer uvs = BufferUtils.createFloatBuffer(2 * numVertices); - debugMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); - for (int vertexI = 0; vertexI < numVertices; ++vertexI) { - float x = positions.get(numAxes * vertexI); - float z = positions.get(numAxes * vertexI + 2); - float u = 12f * x; - float v = 12f * z; - uvs.put(u).put(v); - } - uvs.flip(); - } - }; - /** - * mass of each soft body (>0) - */ - final private static float mass = 1f; - /** - * indices of the waistline vertices in the Puppet model, arranged clockwise - * as seen from above, starting at her right hip - */ - final private static int[] waistlineVertices = { - 2396, 2394, 569, 545, 553, 554, 562, 2401}; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestSoftBody.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = TestSoftBody.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * lines of text displayed in the upper-left corner of the GUI node ([0] is - * the top line) - */ - final private static BitmapText[] statusLines - = new BitmapText[numStatusLines]; - /** - * invisible physics objects - */ - final private static FilterAll hiddenObjects = new FilterAll(true); - /** - * space for physics simulation - */ - private static PhysicsSoftSpace physicsSpace; - /** - * AppState to manage the PhysicsSpace - */ - private static SoftPhysicsAppState bulletAppState; - /** - * name of the test being run - */ - private static String testName = "puppetInSkirt"; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestSoftBody application. - */ - public TestSoftBody() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestSoftBody application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - // Enable direct-memory tracking. - BufferUtils.setTrackDirectMemoryEnabled(true); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - settings.setVSync(false); - - Application application = new TestSoftBody(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - addStatusLines(); - super.acorusInit(); - - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - addLighting(rootNode, false); - - float halfExtent = 4f; - float topY = 0f; - attachCubePlatform(halfExtent, topY); - - DynamicAnimControl dac = addPuppet(); - addSkirt(dac); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.MatParams, true); - //dumper.setEnabled(DumpFlags.NativeIDs, true); - //dumper.setEnabled(DumpFlags.NodesInSofts, true); - dumper.setEnabled(DumpFlags.ShadowModes, true); - dumper.setEnabled(DumpFlags.Transforms, true); - } - - /** - * Calculate screen bounds for a detailed help node. - * - * @param viewPortWidth (in pixels, >0) - * @param viewPortHeight (in pixels, >0) - * @return a new instance - */ - @Override - public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { - // Position help nodes just below the status lines. - float margin = 10f; // in pixels - float width = viewPortWidth - 2f * margin; - float height = viewPortHeight - (2f * margin + numStatusLines * 20f); - float leftX = margin; - float topY = margin + height; - Rectangle result = new Rectangle(leftX, topY, width, height); - - return result; - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - Texture plaid - = MyAsset.loadTexture(assetManager, "Textures/plaid.png", true); - plaid.setAnisotropicFilter(8); - plaid.setWrap(Texture.WrapMode.Repeat); - Material plaidMaterial - = MyAsset.createShadedMaterial(assetManager, plaid); - RenderState renderState = plaidMaterial.getAdditionalRenderState(); - renderState.setFaceCullMode(RenderState.FaceCullMode.Off); - registerMaterial("plaid", plaidMaterial); - - Texture texture = MyAsset.loadTexture( - assetManager, "Interface/Logo/Monkey.png", true); - Material logoMaterial - = MyAsset.createShadedMaterial(assetManager, texture); - renderState = logoMaterial.getAdditionalRenderState(); - renderState.setFaceCullMode(RenderState.FaceCullMode.Off); - registerMaterial("logo", logoMaterial); - - ColorRGBA pink = new ColorRGBA(1.2f, 0.2f, 0.1f, 1f); - Material pinkMaterial = MyAsset.createShinyMaterial(assetManager, pink); - pinkMaterial.setFloat("Shininess", 4f); - renderState = pinkMaterial.getAdditionalRenderState(); - renderState.setFaceCullMode(RenderState.FaceCullMode.Off); - registerMaterial("pink", pinkMaterial); - - ColorRGBA red = new ColorRGBA(0.7f, 0.01f, 0.01f, 1f); - Material redMaterial = MyAsset.createShadedMaterial(assetManager, red); - redMaterial.setColor( - "Specular", new ColorRGBA(0.05f, 0.05f, 0.05f, 1f)); - redMaterial.setFloat("Shininess", 4f); - renderState = redMaterial.getAdditionalRenderState(); - renderState.setFaceCullMode(RenderState.FaceCullMode.Off); - registerMaterial("red", redMaterial); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 0.5f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind(asCollectGarbage, KeyInput.KEY_G); - dim.bind("go limp", KeyInput.KEY_SPACE); - dim.bind("next", KeyInput.KEY_N); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind("test poleAndFlag", KeyInput.KEY_F3); - dim.bind("test puppetInSkirt", KeyInput.KEY_F4); - dim.bind("test squishyBall", KeyInput.KEY_F1); - dim.bind("test tablecloth", KeyInput.KEY_F2); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - final float halfExtent = 4f; - float topY; - - if (ongoing) { - switch (actionString) { - case "go limp": - goLimp(); - return; - - case "next": - nextPuppetAnimation(); - nextWindVelocity(); - return; - - case "test poleAndFlag": - testName = "poleAndFlag"; - cleanupAfterTest(); - - float length = 0.4f; - attachWorldAxes(length); - - topY = -2f; - attachCubePlatform(halfExtent, topY); - - addPoleAndFlag(); - return; - - case "test puppetInSkirt": - testName = "puppetInSkirt"; - cleanupAfterTest(); - - topY = 0f; - attachCubePlatform(halfExtent, topY); - - DynamicAnimControl dac = addPuppet(); - addSkirt(dac); - return; - - case "test squishyBall": - testName = "squishyBall"; - cleanupAfterTest(); - - topY = 0f; - attachCubePlatform(halfExtent, topY); - - addSquishyBall(1.5f); - return; - - case "test tablecloth": - testName = "tablecloth"; - cleanupAfterTest(); - - topY = -1f; - attachCubePlatform(halfExtent, topY); - - topY = 1.7f; - addCylinder(topY); - - float startY = 2f; - addTablecloth(startY); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - - /** - * Update the GUI layout and proposed settings after a resize. - * - * @param newWidth the new width of the framebuffer (in pixels, >0) - * @param newHeight the new height of the framebuffer (in pixels, >0) - */ - @Override - public void onViewPortResize(int newWidth, int newHeight) { - for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { - float y = newHeight - 20f * lineIndex; - statusLines[lineIndex].setLocalTranslation(0f, y, 0f); - } - - super.onViewPortResize(newWidth, newHeight); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - updateStatusLines(); - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - boolean shadowFlag = true; - addLighting(physicsDebugRootNode, shadowFlag); - } - // ************************************************************************* - // private methods - - /** - * Add a static cylinder to the scene, to serve as an obstacle. - * - * @param topY the Y coordinate of the top surface (in physics-space - * coordinates) - */ - private static void addCylinder(float topY) { - float radius = 1f; - float height = 0.4f; - CollisionShape shape = new CylinderCollisionShape( - radius, height, PhysicsSpace.AXIS_Y); - PhysicsRigidBody cylinderBody - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - Vector3f translation = new Vector3f(0f, topY - height / 2f, 0f); - cylinderBody.setPhysicsLocation(translation); - - physicsSpace.addCollisionObject(cylinderBody); - hiddenObjects.addException(cylinderBody); - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - * @param shadowFlag if true, add a shadow renderer to the default viewport - */ - private void addLighting(Spatial rootSpatial, boolean shadowFlag) { - ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); - Vector3f direction = new Vector3f(1f, -2f, -2f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - rootSpatial.addLight(sun); - sun.setName("sun"); - - rootSpatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - if (shadowFlag) { - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.5f); - viewPort.addProcessor(dlsr); - } - } - - /** - * Add a static pole with a rectangular flag. - */ - private void addPoleAndFlag() { - float radius = 0.06f; - float height = 4f; - CollisionShape shape = new CylinderCollisionShape( - radius, height, PhysicsSpace.AXIS_Y); - PhysicsRigidBody polePrb - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - ColorRGBA color = new ColorRGBA(0.7f, 0.7f, 1f, 1f); - Material material = MyAsset.createShadedMaterial(assetManager, color); - polePrb.setDebugMaterial(material); - polePrb.setDebugMeshNormals(MeshNormals.Smooth); - - physicsSpace.addCollisionObject(polePrb); - - int xLines = 20; - int zLines = 2 * xLines; // 2x as wide as it is tall - float width = 2f; - float spacing = width / zLines; - Mesh mesh = new ClothGrid(xLines, zLines, spacing); - PhysicsSoftBody flagPsb = new PhysicsSoftBody(); - - NativeSoftBodyUtil.appendFromTriMesh(mesh, flagPsb); - flagPsb.setMargin(0.1f); - flagPsb.setMass(mass); - - Vector3f wind = new Vector3f(2.5f, 0f, -0.5f); - flagPsb.setWindVelocity(wind); - - SoftBodyConfig config = flagPsb.getSoftConfig(); - config.set(Sbcp.Damping, 0.01f); - config.set(Sbcp.Drag, 0.5f); - config.set(Sbcp.Lift, 1f); - config.setAerodynamics(Aero.F_TwoSidedLiftDrag); - config.setPositionIterations(3); - - SoftBodyMaterial softMaterial = flagPsb.getSoftMaterial(); - softMaterial.setAngularStiffness(0f); - - Material logoMaterial = findMaterial("logo"); - flagPsb.setDebugMaterial(logoMaterial); - flagPsb.setDebugMeshInitListener(flagDmiListener); - flagPsb.setDebugMeshNormals(MeshNormals.Smooth); - - Quaternion rotation - = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); - flagPsb.applyRotation(rotation); - flagPsb.setPhysicsLocation(new Vector3f(1f, 1.5f, 0f)); - - physicsSpace.addCollisionObject(flagPsb); - - // Add 2 anchors that join the flag to the pole. - boolean allowCollisions = true; - int nodeIndex = 0; // upper left corner of flag - Vector3f initialLocation = flagPsb.nodeLocation(nodeIndex, null); - Anchor anchor0 = new Anchor( - flagPsb, nodeIndex, polePrb, initialLocation, allowCollisions); - physicsSpace.addJoint(anchor0); - - nodeIndex = xLines - 1; // lower left corner of flag - flagPsb.nodeLocation(nodeIndex, initialLocation); - Anchor anchor1 = new Anchor( - flagPsb, nodeIndex, polePrb, initialLocation, allowCollisions); - physicsSpace.addJoint(anchor1); - } - - /** - * Add a Puppet model. - * - * @return the new instance (not null) - */ - private DynamicAnimControl addPuppet() { - // Load the model in "T" pose. - Spatial cgModel = assetManager.loadModel("Models/Puppet/Puppet.j3o"); - AnimMigrationUtils.migrate(cgModel); - rootNode.attachChild(cgModel); - SkinningControl sc = (SkinningControl) RagUtils.findSControl(cgModel); - Spatial controlledSpatial = sc.getSpatial(); - - // Configure and add her physics control. - DynamicAnimControl dac = new PuppetControl(); - controlledSpatial.addControl(dac); - dac.setPhysicsSpace(physicsSpace); - - // Don't visualize her rigid bodies. - PhysicsRigidBody[] rigids = dac.listRigidBodies(); - for (PhysicsRigidBody rigid : rigids) { - hiddenObjects.addException(rigid); - } - - return dac; - } - - /** - * Add a wraparound skirt to the specified Puppet model. - * - * @param puppetDac the model's physics control (not null) - */ - private void addSkirt(DynamicAnimControl puppetDac) { - int numDivisions = 5; - int numAnchors = 51; - float length = 0.57f; - Vector3f[] anchorLocs = new Vector3f[numAnchors]; - for (int zIndex = 0; zIndex < numAnchors; ++zIndex) { - anchorLocs[zIndex] = new Vector3f(); - } - Mesh mesh - = createSkirtMesh(puppetDac, numDivisions, length, anchorLocs); - - // Create and configure the soft body. - PhysicsSoftBody skirtPsb = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, skirtPsb); - skirtPsb.setMargin(0.1f); - skirtPsb.setMass(0.02f); - - SoftBodyConfig config = skirtPsb.getSoftConfig(); - config.set(Sbcp.AnchorHardness, 1f); - config.set(Sbcp.KineticHardness, 1f); - config.setPositionIterations(6); - - SoftBodyMaterial material = skirtPsb.getSoftMaterial(); - material.setAngularStiffness(0f); - material.setLinearStiffness(0.5f); - - Material redMaterial = findMaterial("red"); - skirtPsb.setDebugMaterial(redMaterial); - skirtPsb.setDebugMeshNormals(MeshNormals.Smooth); - - String vSpec0 = puppetVSpec(0); - PhysicsLink link = puppetDac.findManagerForVertex(vSpec0, null, null); - Transform localToWorld = link.physicsTransform(null); - skirtPsb.applyTransform(localToWorld); - - physicsSpace.addCollisionObject(skirtPsb); - skirtPsb.setGravity(new Vector3f(0f, -10f, 0f)); - - // Add anchors that join Puppet to her skirt. - PhysicsRigidBody rigid = link.getRigidBody(); - boolean allowCollisions = true; - for (int anchorIndex = 0; anchorIndex < numAnchors; ++anchorIndex) { - Vector3f location = anchorLocs[anchorIndex]; - Anchor anchor = new Anchor( - skirtPsb, anchorIndex, rigid, location, allowCollisions); - physicsSpace.addJoint(anchor); - } - } - - /** - * Add a squishy ball to the scene. - * - * @param startY the desired initial Y coordinate of the center - */ - private void addSquishyBall(float startY) { - int numRefinementIterations = 3; - float radius = 0.5f; - Mesh mesh = new Icosphere(numRefinementIterations, radius); - PhysicsSoftBody ballPsb = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, ballPsb); - ballPsb.setMass(mass); - - SoftBodyConfig config = ballPsb.getSoftConfig(); - config.set(Sbcp.PoseMatching, 0.02f); - config.set(Sbcp.KineticHardness, 1f); - - boolean setVolumePose = false; - boolean setFramePose = true; - ballPsb.setPose(setVolumePose, setFramePose); - - Material pinkMaterial = findMaterial("pink"); - ballPsb.setDebugMaterial(pinkMaterial); - ballPsb.setDebugMeshNormals(MeshNormals.Smooth); - - Vector3f translation = new Vector3f(0f, startY, 0f); - ballPsb.applyTranslation(translation); - - physicsSpace.addCollisionObject(ballPsb); - } - - /** - * Add status lines to the GUI. - */ - private void addStatusLines() { - for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { - statusLines[lineIndex] = new BitmapText(guiFont); - guiNode.attachChild(statusLines[lineIndex]); - } - } - - /** - * Add a square tablecloth to the scene. - * - * @param startY the desired initial Y coordinate of the cloth - */ - private void addTablecloth(float startY) { - int numLines = 40; - float spacing = 0.08f; - Mesh mesh = new ClothGrid(numLines, numLines, spacing); - PhysicsSoftBody softBody = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); - softBody.setMass(mass); - - SoftBodyConfig config = softBody.getSoftConfig(); - config.set(Sbcp.Damping, 0.02f); - config.setPositionIterations(3); - - SoftBodyMaterial material = softBody.getSoftMaterial(); - material.setAngularStiffness(0f); - - Material plaidMaterial = findMaterial("plaid"); - softBody.setDebugMaterial(plaidMaterial); - softBody.setDebugMeshInitListener(tableclothDmiListener); - softBody.setDebugMeshNormals(MeshNormals.Smooth); - - Vector3f translation = new Vector3f(0f, startY, 0f); - softBody.applyTranslation(translation); - - physicsSpace.addCollisionObject(softBody); - } - - /** - * Clean up after a test. - */ - private void cleanupAfterTest() { - // Remove any scenery. Debug meshes are under a different root node. - rootNode.detachAllChildren(); - - // Remove physics objects, which also removes their debug meshes. - physicsSpace.destroy(); - assert physicsSpace.isEmpty(); - - // Clear the hidden-object list. - hiddenObjects.clearExceptions(); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float near = 0.02f; - float far = 20f; - MyCamera.setNearFar(cam, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(2f); - flyCam.setZoomSpeed(2f); - - cam.setLocation(new Vector3f(0f, 2.2f, 3.9f)); - cam.setRotation(new Quaternion(0f, 0.98525f, -0.172f, 0f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - CollisionShape.setDefaultMargin(0.005f); // 5-mm margin - - bulletAppState = new SoftPhysicsAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugFilter(hiddenObjects); - bulletAppState.setDebugInitListener(this); - stateManager.attach(bulletAppState); - - physicsSpace = bulletAppState.getPhysicsSoftSpace(); - physicsSpace.setAccuracy(0.01f); // 10-msec timestep - setGravityAll(1f); - } - - /** - * Generate a Mesh for Puppet's skirt. - * - * @param puppetDac the model's physics control (not null) - * @param zStep the number of mesh squares between successive anchors - * (≥1) - * @param skirtLength the desired length (in physics-space units, &get;0) - * @param local storage for waist locations in local coordinates (not null, - * modified) - * @return a new Mesh, fitted to the model in her bone's local coordinates - */ - private static Mesh createSkirtMesh(DynamicAnimControl puppetDac, - int zStep, float skirtLength, Vector3f[] local) { - int numWaistVerts = waistlineVertices.length; - int numXLines = local.length; - - for (Vector3f vector3f : local) { - vector3f.zero(); - } - /* - * Calculate locations (in local coordinates of the - * rigid body) of the mesh vertices on Puppet's waistline. - */ - float raiseWaistline = 0.04f; - String vSpec = puppetVSpec(0); - PhysicsLink link0 = puppetDac.findManagerForVertex(vSpec, null, null); - for (int zIndex = 0; zIndex < numXLines; zIndex += zStep) { - int waistVertI = (zIndex / zStep) % numWaistVerts; - vSpec = puppetVSpec(waistVertI); - Vector3f location = local[zIndex]; - PhysicsLink link - = puppetDac.findManagerForVertex(vSpec, null, location); - assert link == link0 : link; - location.z -= raiseWaistline; - } - /* - * Expand the waistline outward (from the apex of the cone) - * to provide a margin. Also, compute maxRadius. - */ - float margin = 0.02f; - BoundingBox aabb = MyArray.aabb(local, null); - Vector3f apex = aabb.getCenter(); // alias - float raiseApex = 0.5f; // 0 = full-circle skirt, 0.5 = not very full - apex.z -= raiseApex; - float maxRadius = 0f; - for (int zIndex = 0; zIndex < numXLines; zIndex += zStep) { - float radius = local[zIndex].distance(apex); - float expandedRadius = radius + margin; - local[zIndex].multLocal(expandedRadius / radius); - - maxRadius = Math.max(expandedRadius, maxRadius); - } - /* - * Subdivide the waistline, interpolating zStep-1 soft-body nodes - * between each pair of successive Puppet vertices. - * Also, calculate its circumference. - */ - float circumference = 0f; - for (int zIndex = zStep; zIndex < numXLines; zIndex += zStep) { - int prevZi = zIndex - zStep; - float vSpacing = local[zIndex].distance(local[prevZi]); - circumference += vSpacing; - - for (int zi = prevZi + 1; zi < zIndex; ++zi) { - float t = ((float) (zi - prevZi)) / zStep; - MyVector3f.lerp(t, local[prevZi], local[zIndex], local[zi]); - } - } - - // Generate the mesh topology. - float averageSpacing = circumference / (numXLines - 1); - int numZLines = Math.round(0.3f * skirtLength / averageSpacing); - ClothGrid mesh = new ClothGrid(numXLines, numZLines, averageSpacing); - /* - * Scale the waistline outward from the apex. - * Apply the resulting locations to deform the ClothGrid - * into a roughly conical shape. - */ - float hemRadius = maxRadius + skirtLength; - Vector3f offset = new Vector3f(); - Vector3f hemLocation = new Vector3f(); - Vector3f tmpLocation = new Vector3f(); - for (int zIndex = 0; zIndex < numXLines; ++zIndex) { - local[zIndex].subtract(apex, offset); - float waistRadius = offset.length(); - offset.multLocal(hemRadius / waistRadius); - offset.add(apex, hemLocation); - - for (int xIndex = 0; xIndex < numZLines; ++xIndex) { - float t = ((float) xIndex) / (numZLines - 1); - MyVector3f.lerp(t, local[zIndex], hemLocation, tmpLocation); - mesh.reposition(xIndex, zIndex, tmpLocation); - } - } - - return mesh; - } - - /** - * If the scene contains exactly one DynamicAnimControl, and it's ready to - * go dynamic, put it into ragdoll mode. - */ - private void goLimp() { - List dacs = MySpatial.listControls( - rootNode, DynamicAnimControl.class, null); - if (dacs.size() == 1) { - DynamicAnimControl dac = dacs.get(0); - if (dac.isReady()) { - dac.setRagdollMode(); - } - } - } - - /** - * Cycle through animations of the Puppet model. - */ - private void nextPuppetAnimation() { - SkinningControl sc = (SkinningControl) RagUtils.findSControl(rootNode); - if (sc == null) { - return; - } - - Spatial controlledSpatial = sc.getSpatial(); - AnimComposer composer - = controlledSpatial.getControl(AnimComposer.class); - - Action jog = composer.action("jog"); - Action walk = composer.action("walk"); - - Action action = composer.getCurrentAction(); - if (action == null) { // first time - composer.setCurrentAction("jog"); - } else if (action == jog) { - composer.setCurrentAction("walk"); - } else if (action == walk) { - composer.setCurrentAction("jog"); - } - } - - /** - * Cycle through wind velocities. - */ - private static void nextWindVelocity() { - Collection softBodies = physicsSpace.getSoftBodyList(); - for (PhysicsSoftBody softBody : softBodies) { - Vector3f windVelocity = softBody.windVelocity(null); - float windSpeed = windVelocity.length(); - if (windSpeed > 1f) { - windVelocity.divideLocal(4f); - } else { - windVelocity.multLocal(4f); - } - softBody.setWindVelocity(windVelocity); - } - } - - /** - * Generate a specifier for the indexed vertex on Puppet's waistline. - * - * @param waistIndex (≥0, <numWaistVertices) - * @return the specifier string - */ - private static String puppetVSpec(int waistIndex) { - int puppetVertex = waistlineVertices[waistIndex]; - String vertexSpecifier = String.format("%d/Mesh.009_0", puppetVertex); - - return vertexSpecifier; - } - - /** - * Update the status lines in the GUI. - */ - private void updateStatusLines() { - String message = String.format( - "Test: %s%s", testName, isPaused() ? " PAUSED" : ""); - statusLines[0].setText(message); - - String viewOptions = describePhysicsDebugOptions(); - message = "View: " + viewOptions; - statusLines[1].setText(message); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.Application; +import com.jme3.app.state.AppState; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.animation.RagUtils; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.debug.DebugMeshInitListener; +import com.jme3.bullet.joints.Anchor; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.Aero; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.font.BitmapText; +import com.jme3.font.Rectangle; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.math.MyArray; +import jme3utilities.math.MyVector3f; +import jme3utilities.mesh.ClothGrid; +import jme3utilities.mesh.Icosphere; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.FilterAll; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.minie.test.tunings.PuppetControl; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; + +/** + * Test/demonstrate soft-body physics. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestSoftBody + extends PhysicsDemo + implements DebugInitListener { + // ************************************************************************* + // constants and loggers + + /** + * number of axes in a vector + */ + final private static int numAxes = 3; + /** + * number of lines of text in the overlay + */ + final private static int numStatusLines = 2; + /** + * add UVs to the debug mesh of a flag + */ + final private static DebugMeshInitListener flagDmiListener + = new DebugMeshInitListener() { + @Override + public void debugMeshInit(Mesh debugMesh) { + FloatBuffer positions + = debugMesh.getFloatBuffer(VertexBuffer.Type.Position); + int numVertices = positions.limit() / numAxes; + FloatBuffer uvs = BufferUtils.createFloatBuffer(2 * numVertices); + debugMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); + for (int vertexI = 0; vertexI < numVertices; ++vertexI) { + float x = positions.get(numAxes * vertexI); + float y = positions.get(numAxes * vertexI + 1); + float u = x - 0.5f; + float v = 2f - y; + uvs.put(u).put(v); + } + uvs.flip(); + } + }; + /** + * add UVs to the debug mesh of a tablecloth + */ + final private static DebugMeshInitListener tableclothDmiListener + = new DebugMeshInitListener() { + @Override + public void debugMeshInit(Mesh debugMesh) { + FloatBuffer positions + = debugMesh.getFloatBuffer(VertexBuffer.Type.Position); + int numVertices = positions.limit() / numAxes; + FloatBuffer uvs = BufferUtils.createFloatBuffer(2 * numVertices); + debugMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uvs); + for (int vertexI = 0; vertexI < numVertices; ++vertexI) { + float x = positions.get(numAxes * vertexI); + float z = positions.get(numAxes * vertexI + 2); + float u = 12f * x; + float v = 12f * z; + uvs.put(u).put(v); + } + uvs.flip(); + } + }; + /** + * mass of each soft body (>0) + */ + final private static float mass = 1f; + /** + * indices of the waistline vertices in the Puppet model, arranged clockwise + * as seen from above, starting at her right hip + */ + final private static int[] waistlineVertices = { + 2396, 2394, 569, 545, 553, 554, 562, 2401}; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestSoftBody.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = TestSoftBody.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * lines of text displayed in the upper-left corner of the GUI node ([0] is + * the top line) + */ + final private static BitmapText[] statusLines + = new BitmapText[numStatusLines]; + /** + * invisible physics objects + */ + final private static FilterAll hiddenObjects = new FilterAll(true); + /** + * space for physics simulation + */ + private static PhysicsSoftSpace physicsSpace; + /** + * AppState to manage the PhysicsSpace + */ + private static SoftPhysicsAppState bulletAppState; + /** + * name of the test being run + */ + private static String testName = "puppetInSkirt"; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestSoftBody application. + */ + public TestSoftBody() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestSoftBody application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + // Enable direct-memory tracking. + BufferUtils.setTrackDirectMemoryEnabled(true); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + settings.setVSync(false); + + Application application = new TestSoftBody(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + addStatusLines(); + super.acorusInit(); + + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + addLighting(rootNode, false); + + float halfExtent = 4f; + float topY = 0f; + attachCubePlatform(halfExtent, topY); + + DynamicAnimControl dac = addPuppet(); + addSkirt(dac); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.MatParams, true); + //dumper.setEnabled(DumpFlags.NativeIDs, true); + //dumper.setEnabled(DumpFlags.NodesInSofts, true); + dumper.setEnabled(DumpFlags.ShadowModes, true); + dumper.setEnabled(DumpFlags.Transforms, true); + } + + /** + * Calculate screen bounds for a detailed help node. + * + * @param viewPortWidth (in pixels, >0) + * @param viewPortHeight (in pixels, >0) + * @return a new instance + */ + @Override + public Rectangle detailedHelpBounds(int viewPortWidth, int viewPortHeight) { + // Position help nodes just below the status lines. + float margin = 10f; // in pixels + float width = viewPortWidth - 2f * margin; + float height = viewPortHeight - (2f * margin + numStatusLines * 20f); + float leftX = margin; + float topY = margin + height; + Rectangle result = new Rectangle(leftX, topY, width, height); + + return result; + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + Texture plaid + = MyAsset.loadTexture(assetManager, "Textures/plaid.png", true); + plaid.setAnisotropicFilter(8); + plaid.setWrap(Texture.WrapMode.Repeat); + Material plaidMaterial + = MyAsset.createShadedMaterial(assetManager, plaid); + RenderState renderState = plaidMaterial.getAdditionalRenderState(); + renderState.setFaceCullMode(RenderState.FaceCullMode.Off); + registerMaterial("plaid", plaidMaterial); + + Texture texture = MyAsset.loadTexture( + assetManager, "Interface/Logo/Monkey.png", true); + Material logoMaterial + = MyAsset.createShadedMaterial(assetManager, texture); + renderState = logoMaterial.getAdditionalRenderState(); + renderState.setFaceCullMode(RenderState.FaceCullMode.Off); + registerMaterial("logo", logoMaterial); + + ColorRGBA pink = new ColorRGBA(1.2f, 0.2f, 0.1f, 1f); + Material pinkMaterial = MyAsset.createShinyMaterial(assetManager, pink); + pinkMaterial.setFloat("Shininess", 4f); + renderState = pinkMaterial.getAdditionalRenderState(); + renderState.setFaceCullMode(RenderState.FaceCullMode.Off); + registerMaterial("pink", pinkMaterial); + + ColorRGBA red = new ColorRGBA(0.7f, 0.01f, 0.01f, 1f); + Material redMaterial = MyAsset.createShadedMaterial(assetManager, red); + redMaterial.setColor( + "Specular", new ColorRGBA(0.05f, 0.05f, 0.05f, 1f)); + redMaterial.setFloat("Shininess", 4f); + renderState = redMaterial.getAdditionalRenderState(); + renderState.setFaceCullMode(RenderState.FaceCullMode.Off); + registerMaterial("red", redMaterial); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 0.5f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind(asCollectGarbage, KeyInput.KEY_G); + dim.bind("go limp", KeyInput.KEY_SPACE); + dim.bind("next", KeyInput.KEY_N); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind("test poleAndFlag", KeyInput.KEY_F3); + dim.bind("test puppetInSkirt", KeyInput.KEY_F4); + dim.bind("test squishyBall", KeyInput.KEY_F1); + dim.bind("test tablecloth", KeyInput.KEY_F2); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + final float halfExtent = 4f; + float topY; + + if (ongoing) { + switch (actionString) { + case "go limp": + goLimp(); + return; + + case "next": + nextPuppetAnimation(); + nextWindVelocity(); + return; + + case "test poleAndFlag": + testName = "poleAndFlag"; + cleanupAfterTest(); + + float length = 0.4f; + attachWorldAxes(length); + + topY = -2f; + attachCubePlatform(halfExtent, topY); + + addPoleAndFlag(); + return; + + case "test puppetInSkirt": + testName = "puppetInSkirt"; + cleanupAfterTest(); + + topY = 0f; + attachCubePlatform(halfExtent, topY); + + DynamicAnimControl dac = addPuppet(); + addSkirt(dac); + return; + + case "test squishyBall": + testName = "squishyBall"; + cleanupAfterTest(); + + topY = 0f; + attachCubePlatform(halfExtent, topY); + + addSquishyBall(1.5f); + return; + + case "test tablecloth": + testName = "tablecloth"; + cleanupAfterTest(); + + topY = -1f; + attachCubePlatform(halfExtent, topY); + + topY = 1.7f; + addCylinder(topY); + + float startY = 2f; + addTablecloth(startY); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + + /** + * Update the GUI layout and proposed settings after a resize. + * + * @param newWidth the new width of the framebuffer (in pixels, >0) + * @param newHeight the new height of the framebuffer (in pixels, >0) + */ + @Override + public void onViewPortResize(int newWidth, int newHeight) { + for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { + float y = newHeight - 20f * lineIndex; + statusLines[lineIndex].setLocalTranslation(0f, y, 0f); + } + + super.onViewPortResize(newWidth, newHeight); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + updateStatusLines(); + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + boolean shadowFlag = true; + addLighting(physicsDebugRootNode, shadowFlag); + } + // ************************************************************************* + // private methods + + /** + * Add a static cylinder to the scene, to serve as an obstacle. + * + * @param topY the Y coordinate of the top surface (in physics-space + * coordinates) + */ + private static void addCylinder(float topY) { + float radius = 1f; + float height = 0.4f; + CollisionShape shape = new CylinderCollisionShape( + radius, height, PhysicsSpace.AXIS_Y); + PhysicsRigidBody cylinderBody + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + Vector3f translation = new Vector3f(0f, topY - height / 2f, 0f); + cylinderBody.setPhysicsLocation(translation); + + physicsSpace.addCollisionObject(cylinderBody); + hiddenObjects.addException(cylinderBody); + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + * @param shadowFlag if true, add a shadow renderer to the default viewport + */ + private void addLighting(Spatial rootSpatial, boolean shadowFlag) { + ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); + Vector3f direction = new Vector3f(1f, -2f, -2f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + rootSpatial.addLight(sun); + sun.setName("sun"); + + rootSpatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + if (shadowFlag) { + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.5f); + viewPort.addProcessor(dlsr); + } + } + + /** + * Add a static pole with a rectangular flag. + */ + private void addPoleAndFlag() { + float radius = 0.06f; + float height = 4f; + CollisionShape shape = new CylinderCollisionShape( + radius, height, PhysicsSpace.AXIS_Y); + PhysicsRigidBody polePrb + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + ColorRGBA color = new ColorRGBA(0.7f, 0.7f, 1f, 1f); + Material material = MyAsset.createShadedMaterial(assetManager, color); + polePrb.setDebugMaterial(material); + polePrb.setDebugMeshNormals(MeshNormals.Smooth); + + physicsSpace.addCollisionObject(polePrb); + + int xLines = 20; + int zLines = 2 * xLines; // 2x as wide as it is tall + float width = 2f; + float spacing = width / zLines; + Mesh mesh = new ClothGrid(xLines, zLines, spacing); + PhysicsSoftBody flagPsb = new PhysicsSoftBody(); + + NativeSoftBodyUtil.appendFromTriMesh(mesh, flagPsb); + flagPsb.setMargin(0.1f); + flagPsb.setMass(mass); + + Vector3f wind = new Vector3f(2.5f, 0f, -0.5f); + flagPsb.setWindVelocity(wind); + + SoftBodyConfig config = flagPsb.getSoftConfig(); + config.set(Sbcp.Damping, 0.01f); + config.set(Sbcp.Drag, 0.5f); + config.set(Sbcp.Lift, 1f); + config.setAerodynamics(Aero.F_TwoSidedLiftDrag); + config.setPositionIterations(3); + + SoftBodyMaterial softMaterial = flagPsb.getSoftMaterial(); + softMaterial.setAngularStiffness(0f); + + Material logoMaterial = findMaterial("logo"); + flagPsb.setDebugMaterial(logoMaterial); + flagPsb.setDebugMeshInitListener(flagDmiListener); + flagPsb.setDebugMeshNormals(MeshNormals.Smooth); + + Quaternion rotation + = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); + flagPsb.applyRotation(rotation); + flagPsb.setPhysicsLocation(new Vector3f(1f, 1.5f, 0f)); + + physicsSpace.addCollisionObject(flagPsb); + + // Add 2 anchors that join the flag to the pole. + boolean allowCollisions = true; + int nodeIndex = 0; // upper left corner of flag + Vector3f initialLocation = flagPsb.nodeLocation(nodeIndex, null); + Anchor anchor0 = new Anchor( + flagPsb, nodeIndex, polePrb, initialLocation, allowCollisions); + physicsSpace.addJoint(anchor0); + + nodeIndex = xLines - 1; // lower left corner of flag + flagPsb.nodeLocation(nodeIndex, initialLocation); + Anchor anchor1 = new Anchor( + flagPsb, nodeIndex, polePrb, initialLocation, allowCollisions); + physicsSpace.addJoint(anchor1); + } + + /** + * Add a Puppet model. + * + * @return the new instance (not null) + */ + private DynamicAnimControl addPuppet() { + // Load the model in "T" pose. + Spatial cgModel = assetManager.loadModel("Models/Puppet/Puppet.j3o"); + AnimMigrationUtils.migrate(cgModel); + rootNode.attachChild(cgModel); + SkinningControl sc = (SkinningControl) RagUtils.findSControl(cgModel); + Spatial controlledSpatial = sc.getSpatial(); + + // Configure and add her physics control. + DynamicAnimControl dac = new PuppetControl(); + controlledSpatial.addControl(dac); + dac.setPhysicsSpace(physicsSpace); + + // Don't visualize her rigid bodies. + PhysicsRigidBody[] rigids = dac.listRigidBodies(); + for (PhysicsRigidBody rigid : rigids) { + hiddenObjects.addException(rigid); + } + + return dac; + } + + /** + * Add a wraparound skirt to the specified Puppet model. + * + * @param puppetDac the model's physics control (not null) + */ + private void addSkirt(DynamicAnimControl puppetDac) { + int numDivisions = 5; + int numAnchors = 51; + float length = 0.57f; + Vector3f[] anchorLocs = new Vector3f[numAnchors]; + for (int zIndex = 0; zIndex < numAnchors; ++zIndex) { + anchorLocs[zIndex] = new Vector3f(); + } + Mesh mesh + = createSkirtMesh(puppetDac, numDivisions, length, anchorLocs); + + // Create and configure the soft body. + PhysicsSoftBody skirtPsb = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, skirtPsb); + skirtPsb.setMargin(0.1f); + skirtPsb.setMass(0.02f); + + SoftBodyConfig config = skirtPsb.getSoftConfig(); + config.set(Sbcp.AnchorHardness, 1f); + config.set(Sbcp.KineticHardness, 1f); + config.setPositionIterations(6); + + SoftBodyMaterial material = skirtPsb.getSoftMaterial(); + material.setAngularStiffness(0f); + material.setLinearStiffness(0.5f); + + Material redMaterial = findMaterial("red"); + skirtPsb.setDebugMaterial(redMaterial); + skirtPsb.setDebugMeshNormals(MeshNormals.Smooth); + + String vSpec0 = puppetVSpec(0); + PhysicsLink link = puppetDac.findManagerForVertex(vSpec0, null, null); + Transform localToWorld = link.physicsTransform(null); + skirtPsb.applyTransform(localToWorld); + + physicsSpace.addCollisionObject(skirtPsb); + skirtPsb.setGravity(new Vector3f(0f, -10f, 0f)); + + // Add anchors that join Puppet to her skirt. + PhysicsRigidBody rigid = link.getRigidBody(); + boolean allowCollisions = true; + for (int anchorIndex = 0; anchorIndex < numAnchors; ++anchorIndex) { + Vector3f location = anchorLocs[anchorIndex]; + Anchor anchor = new Anchor( + skirtPsb, anchorIndex, rigid, location, allowCollisions); + physicsSpace.addJoint(anchor); + } + } + + /** + * Add a squishy ball to the scene. + * + * @param startY the desired initial Y coordinate of the center + */ + private void addSquishyBall(float startY) { + int numRefinementIterations = 3; + float radius = 0.5f; + Mesh mesh = new Icosphere(numRefinementIterations, radius); + PhysicsSoftBody ballPsb = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, ballPsb); + ballPsb.setMass(mass); + + SoftBodyConfig config = ballPsb.getSoftConfig(); + config.set(Sbcp.PoseMatching, 0.02f); + config.set(Sbcp.KineticHardness, 1f); + + boolean setVolumePose = false; + boolean setFramePose = true; + ballPsb.setPose(setVolumePose, setFramePose); + + Material pinkMaterial = findMaterial("pink"); + ballPsb.setDebugMaterial(pinkMaterial); + ballPsb.setDebugMeshNormals(MeshNormals.Smooth); + + Vector3f translation = new Vector3f(0f, startY, 0f); + ballPsb.applyTranslation(translation); + + physicsSpace.addCollisionObject(ballPsb); + } + + /** + * Add status lines to the GUI. + */ + private void addStatusLines() { + for (int lineIndex = 0; lineIndex < statusLines.length; ++lineIndex) { + statusLines[lineIndex] = new BitmapText(guiFont); + guiNode.attachChild(statusLines[lineIndex]); + } + } + + /** + * Add a square tablecloth to the scene. + * + * @param startY the desired initial Y coordinate of the cloth + */ + private void addTablecloth(float startY) { + int numLines = 40; + float spacing = 0.08f; + Mesh mesh = new ClothGrid(numLines, numLines, spacing); + PhysicsSoftBody softBody = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, softBody); + softBody.setMass(mass); + + SoftBodyConfig config = softBody.getSoftConfig(); + config.set(Sbcp.Damping, 0.02f); + config.setPositionIterations(3); + + SoftBodyMaterial material = softBody.getSoftMaterial(); + material.setAngularStiffness(0f); + + Material plaidMaterial = findMaterial("plaid"); + softBody.setDebugMaterial(plaidMaterial); + softBody.setDebugMeshInitListener(tableclothDmiListener); + softBody.setDebugMeshNormals(MeshNormals.Smooth); + + Vector3f translation = new Vector3f(0f, startY, 0f); + softBody.applyTranslation(translation); + + physicsSpace.addCollisionObject(softBody); + } + + /** + * Clean up after a test. + */ + private void cleanupAfterTest() { + // Remove any scenery. Debug meshes are under a different root node. + rootNode.detachAllChildren(); + + // Remove physics objects, which also removes their debug meshes. + physicsSpace.destroy(); + assert physicsSpace.isEmpty(); + + // Clear the hidden-object list. + hiddenObjects.clearExceptions(); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float near = 0.02f; + float far = 20f; + MyCamera.setNearFar(cam, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(2f); + flyCam.setZoomSpeed(2f); + + cam.setLocation(new Vector3f(0f, 2.2f, 3.9f)); + cam.setRotation(new Quaternion(0f, 0.98525f, -0.172f, 0f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + CollisionShape.setDefaultMargin(0.005f); // 5-mm margin + + bulletAppState = new SoftPhysicsAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugFilter(hiddenObjects); + bulletAppState.setDebugInitListener(this); + stateManager.attach(bulletAppState); + + physicsSpace = bulletAppState.getPhysicsSoftSpace(); + physicsSpace.setAccuracy(0.01f); // 10-msec timestep + setGravityAll(1f); + } + + /** + * Generate a Mesh for Puppet's skirt. + * + * @param puppetDac the model's physics control (not null) + * @param zStep the number of mesh squares between successive anchors + * (≥1) + * @param skirtLength the desired length (in physics-space units, &get;0) + * @param local storage for waist locations in local coordinates (not null, + * modified) + * @return a new Mesh, fitted to the model in her bone's local coordinates + */ + private static Mesh createSkirtMesh(DynamicAnimControl puppetDac, + int zStep, float skirtLength, Vector3f[] local) { + int numWaistVerts = waistlineVertices.length; + int numXLines = local.length; + + for (Vector3f vector3f : local) { + vector3f.zero(); + } + /* + * Calculate locations (in local coordinates of the + * rigid body) of the mesh vertices on Puppet's waistline. + */ + float raiseWaistline = 0.04f; + String vSpec = puppetVSpec(0); + PhysicsLink link0 = puppetDac.findManagerForVertex(vSpec, null, null); + for (int zIndex = 0; zIndex < numXLines; zIndex += zStep) { + int waistVertI = (zIndex / zStep) % numWaistVerts; + vSpec = puppetVSpec(waistVertI); + Vector3f location = local[zIndex]; + PhysicsLink link + = puppetDac.findManagerForVertex(vSpec, null, location); + assert link == link0 : link; + location.z -= raiseWaistline; + } + /* + * Expand the waistline outward (from the apex of the cone) + * to provide a margin. Also, compute maxRadius. + */ + float margin = 0.02f; + BoundingBox aabb = MyArray.aabb(local, null); + Vector3f apex = aabb.getCenter(); // alias + float raiseApex = 0.5f; // 0 = full-circle skirt, 0.5 = not very full + apex.z -= raiseApex; + float maxRadius = 0f; + for (int zIndex = 0; zIndex < numXLines; zIndex += zStep) { + float radius = local[zIndex].distance(apex); + float expandedRadius = radius + margin; + local[zIndex].multLocal(expandedRadius / radius); + + maxRadius = Math.max(expandedRadius, maxRadius); + } + /* + * Subdivide the waistline, interpolating zStep-1 soft-body nodes + * between each pair of successive Puppet vertices. + * Also, calculate its circumference. + */ + float circumference = 0f; + for (int zIndex = zStep; zIndex < numXLines; zIndex += zStep) { + int prevZi = zIndex - zStep; + float vSpacing = local[zIndex].distance(local[prevZi]); + circumference += vSpacing; + + for (int zi = prevZi + 1; zi < zIndex; ++zi) { + float t = ((float) (zi - prevZi)) / zStep; + MyVector3f.lerp(t, local[prevZi], local[zIndex], local[zi]); + } + } + + // Generate the mesh topology. + float averageSpacing = circumference / (numXLines - 1); + int numZLines = Math.round(0.3f * skirtLength / averageSpacing); + ClothGrid mesh = new ClothGrid(numXLines, numZLines, averageSpacing); + /* + * Scale the waistline outward from the apex. + * Apply the resulting locations to deform the ClothGrid + * into a roughly conical shape. + */ + float hemRadius = maxRadius + skirtLength; + Vector3f offset = new Vector3f(); + Vector3f hemLocation = new Vector3f(); + Vector3f tmpLocation = new Vector3f(); + for (int zIndex = 0; zIndex < numXLines; ++zIndex) { + local[zIndex].subtract(apex, offset); + float waistRadius = offset.length(); + offset.multLocal(hemRadius / waistRadius); + offset.add(apex, hemLocation); + + for (int xIndex = 0; xIndex < numZLines; ++xIndex) { + float t = ((float) xIndex) / (numZLines - 1); + MyVector3f.lerp(t, local[zIndex], hemLocation, tmpLocation); + mesh.reposition(xIndex, zIndex, tmpLocation); + } + } + + return mesh; + } + + /** + * If the scene contains exactly one DynamicAnimControl, and it's ready to + * go dynamic, put it into ragdoll mode. + */ + private void goLimp() { + List dacs = MySpatial.listControls( + rootNode, DynamicAnimControl.class, null); + if (dacs.size() == 1) { + DynamicAnimControl dac = dacs.get(0); + if (dac.isReady()) { + dac.setRagdollMode(); + } + } + } + + /** + * Cycle through animations of the Puppet model. + */ + private void nextPuppetAnimation() { + SkinningControl sc = (SkinningControl) RagUtils.findSControl(rootNode); + if (sc == null) { + return; + } + + Spatial controlledSpatial = sc.getSpatial(); + AnimComposer composer + = controlledSpatial.getControl(AnimComposer.class); + + Action jog = composer.action("jog"); + Action walk = composer.action("walk"); + + Action action = composer.getCurrentAction(); + if (action == null) { // first time + composer.setCurrentAction("jog"); + } else if (action == jog) { + composer.setCurrentAction("walk"); + } else if (action == walk) { + composer.setCurrentAction("jog"); + } + } + + /** + * Cycle through wind velocities. + */ + private static void nextWindVelocity() { + Collection softBodies = physicsSpace.getSoftBodyList(); + for (PhysicsSoftBody softBody : softBodies) { + Vector3f windVelocity = softBody.windVelocity(null); + float windSpeed = windVelocity.length(); + if (windSpeed > 1f) { + windVelocity.divideLocal(4f); + } else { + windVelocity.multLocal(4f); + } + softBody.setWindVelocity(windVelocity); + } + } + + /** + * Generate a specifier for the indexed vertex on Puppet's waistline. + * + * @param waistIndex (≥0, <numWaistVertices) + * @return the specifier string + */ + private static String puppetVSpec(int waistIndex) { + int puppetVertex = waistlineVertices[waistIndex]; + String vertexSpecifier = String.format("%d/Mesh.009_0", puppetVertex); + + return vertexSpecifier; + } + + /** + * Update the status lines in the GUI. + */ + private void updateStatusLines() { + String message = String.format( + "Test: %s%s", testName, isPaused() ? " PAUSED" : ""); + statusLines[0].setText(message); + + String viewOptions = describePhysicsDebugOptions(); + message = "View: " + viewOptions; + statusLines[1].setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBodyControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBodyControl.java index 19fd74ce2..c82a628bf 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBodyControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBodyControl.java @@ -1,401 +1,401 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.Application; -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.control.SoftBodyControl; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.system.AppSettings; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.FilterAll; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.InputMode; - -/** - * Test/demonstrate the SoftBodyControl class. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestSoftBodyControl - extends PhysicsDemo - implements DebugInitListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestSoftBodyControl.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = TestSoftBodyControl.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * physics objects that are not to be visualized - */ - final private static FilterAll hiddenObjects = new FilterAll(true); - /** - * AppState to manage the PhysicsSpace - */ - private static SoftPhysicsAppState bulletAppState; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestSoftBodyControl application. - */ - public TestSoftBodyControl() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestSoftBodyControl application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - String title = applicationName + " " + MyString.join(arguments); - - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setAudioRenderer(null); - settings.setResizable(true); - settings.setSamples(4); // anti-aliasing - settings.setTitle(title); // Customize the window's title bar. - settings.setVSync(false); - - Application application = new TestSoftBodyControl(); - application.setSettings(settings); - application.start(); - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - super.acorusInit(); - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - addLighting(rootNode, false); - - float halfExtent = 4f; - float topY = 0f; - attachCubePlatform(halfExtent, topY); - - addRubberDuck(); - } - - /** - * Configure the PhysicsDumper during startup. - */ - @Override - public void configureDumper() { - PhysicsDumper dumper = getDumper(); - dumper.setEnabled(DumpFlags.MatParams, true); - dumper.setEnabled(DumpFlags.ShadowModes, true); - dumper.setEnabled(DumpFlags.Transforms, true); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of physics-debug arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 1f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); - dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind("test monkeyHead", KeyInput.KEY_F1); - dim.bind("test rubberDuck", KeyInput.KEY_F2); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - } - - /** - * Process an action that wasn't handled by the active input mode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - float halfExtent = 4f; - float topY = 0f; - if (ongoing) { - switch (actionString) { - case "test monkeyHead": - cleanupAfterTest(); - attachCubePlatform(halfExtent, topY); - addMonkeyHead(); - return; - - case "test rubberDuck": - cleanupAfterTest(); - attachCubePlatform(halfExtent, topY); - addRubberDuck(); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode, true); - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - * @param shadowFlag if true, add a shadow renderer to the default viewport - */ - private void addLighting(Spatial rootSpatial, boolean shadowFlag) { - ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - rootSpatial.addLight(sun); - sun.setName("sun"); - - rootSpatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - if (shadowFlag) { - int mapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, mapSize, numSplits); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.5f); - viewPort.addProcessor(dlsr); - } - } - - /** - * Add a monkey head to the scene. - */ - private void addMonkeyHead() { - Spatial cgModel - = assetManager.loadModel("Models/MonkeyHead/MonkeyHead.j3o"); - cgModel = Heart.deepCopy(cgModel); // clone vertex buffers - rootNode.attachChild(cgModel); - - SoftBodyControl sbc = new SoftBodyControl(); - cgModel.addControl(sbc); - PhysicsSoftBody psb = sbc.getBody(); - - psb.applyScale(new Vector3f(0.6f, 0.6f, 0.6f)); - psb.applyTranslation(new Vector3f(0f, 1.4f, 0f)); - - float totalMass = 1f; - psb.setMassByArea(totalMass); - - SoftBodyConfig config = psb.getSoftConfig(); - config.set(Sbcp.KineticHardness, 1f); - config.set(Sbcp.PoseMatching, 0.03f); - - boolean setVolumePose = false; - boolean setFramePose = true; - psb.setPose(setVolumePose, setFramePose); - - PhysicsSpace space = getPhysicsSpace(); - sbc.setPhysicsSpace(space); - hiddenObjects.addException(sbc); - } - - /** - * Add a rubber duck to the scene. - */ - private void addRubberDuck() { - Spatial cgModel = assetManager.loadModel("Models/Duck/Duck.j3o"); - rootNode.attachChild(cgModel); - - SoftBodyControl sbc = new SoftBodyControl(); - cgModel.addControl(sbc); - PhysicsSoftBody psb = sbc.getBody(); - - float totalMass = 1f; - psb.setMassByArea(totalMass); - - SoftBodyConfig config = psb.getSoftConfig(); - config.set(Sbcp.KineticHardness, 1f); - config.set(Sbcp.PoseMatching, 0.03f); - //config.setPositionIterations(15); - - boolean setVolumePose = false; - boolean setFramePose = true; - psb.setPose(setVolumePose, setFramePose); - - psb.applyRotation(new Quaternion().fromAngles(0.4f, 0f, 1f)); - psb.applyTranslation(new Vector3f(0f, 1.2f, 0f)); - - PhysicsSpace space = getPhysicsSpace(); - sbc.setPhysicsSpace(space); - hiddenObjects.addException(sbc); - } - - /** - * Clean up after a test. - */ - private void cleanupAfterTest() { - // Remove any scenery. Debug meshes are under a different root node. - rootNode.detachAllChildren(); - - // Remove physics objects, which also removes their debug meshes. - PhysicsSpace physicsSpace = getPhysicsSpace(); - physicsSpace.destroy(); - assert physicsSpace.isEmpty(); - - // Clear the hidden-object list. - hiddenObjects.clearExceptions(); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - float yDegrees = MyCamera.yDegrees(cam); - float aspectRatio = MyCamera.viewAspectRatio(cam); - float near = 0.02f; - float far = 20f; - cam.setFrustumPerspective(yDegrees, aspectRatio, near, far); - - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(2f); - flyCam.setZoomSpeed(2f); - - cam.setLocation(new Vector3f(-1.7f, 0.5f, 4.4f)); - cam.setRotation(new Quaternion(-0.0065f, 0.977669f, 0.0283f, 0.20814f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - CollisionShape.setDefaultMargin(0.005f); // 5-mm margin - - bulletAppState = new SoftPhysicsAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugFilter(hiddenObjects); - bulletAppState.setDebugInitListener(this); - stateManager.attach(bulletAppState); - - setGravityAll(1f); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.Application; +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.SoftBodyControl; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.FilterAll; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.InputMode; + +/** + * Test/demonstrate the SoftBodyControl class. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestSoftBodyControl + extends PhysicsDemo + implements DebugInitListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestSoftBodyControl.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = TestSoftBodyControl.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * physics objects that are not to be visualized + */ + final private static FilterAll hiddenObjects = new FilterAll(true); + /** + * AppState to manage the PhysicsSpace + */ + private static SoftPhysicsAppState bulletAppState; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestSoftBodyControl application. + */ + public TestSoftBodyControl() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestSoftBodyControl application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + String title = applicationName + " " + MyString.join(arguments); + + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setAudioRenderer(null); + settings.setResizable(true); + settings.setSamples(4); // anti-aliasing + settings.setTitle(title); // Customize the window's title bar. + settings.setVSync(false); + + Application application = new TestSoftBodyControl(); + application.setSettings(settings); + application.start(); + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + super.acorusInit(); + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + addLighting(rootNode, false); + + float halfExtent = 4f; + float topY = 0f; + attachCubePlatform(halfExtent, topY); + + addRubberDuck(); + } + + /** + * Configure the PhysicsDumper during startup. + */ + @Override + public void configureDumper() { + PhysicsDumper dumper = getDumper(); + dumper.setEnabled(DumpFlags.MatParams, true); + dumper.setEnabled(DumpFlags.ShadowModes, true); + dumper.setEnabled(DumpFlags.Transforms, true); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of physics-debug arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 1f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_DOWN); + dim.bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_UP); + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind("test monkeyHead", KeyInput.KEY_F1); + dim.bind("test rubberDuck", KeyInput.KEY_F2); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + } + + /** + * Process an action that wasn't handled by the active input mode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + float halfExtent = 4f; + float topY = 0f; + if (ongoing) { + switch (actionString) { + case "test monkeyHead": + cleanupAfterTest(); + attachCubePlatform(halfExtent, topY); + addMonkeyHead(); + return; + + case "test rubberDuck": + cleanupAfterTest(); + attachCubePlatform(halfExtent, topY); + addRubberDuck(); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode, true); + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + * @param shadowFlag if true, add a shadow renderer to the default viewport + */ + private void addLighting(Spatial rootSpatial, boolean shadowFlag) { + ColorRGBA ambientColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f); + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + rootSpatial.addLight(sun); + sun.setName("sun"); + + rootSpatial.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + if (shadowFlag) { + int mapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, mapSize, numSplits); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.5f); + viewPort.addProcessor(dlsr); + } + } + + /** + * Add a monkey head to the scene. + */ + private void addMonkeyHead() { + Spatial cgModel + = assetManager.loadModel("Models/MonkeyHead/MonkeyHead.j3o"); + cgModel = Heart.deepCopy(cgModel); // clone vertex buffers + rootNode.attachChild(cgModel); + + SoftBodyControl sbc = new SoftBodyControl(); + cgModel.addControl(sbc); + PhysicsSoftBody psb = sbc.getBody(); + + psb.applyScale(new Vector3f(0.6f, 0.6f, 0.6f)); + psb.applyTranslation(new Vector3f(0f, 1.4f, 0f)); + + float totalMass = 1f; + psb.setMassByArea(totalMass); + + SoftBodyConfig config = psb.getSoftConfig(); + config.set(Sbcp.KineticHardness, 1f); + config.set(Sbcp.PoseMatching, 0.03f); + + boolean setVolumePose = false; + boolean setFramePose = true; + psb.setPose(setVolumePose, setFramePose); + + PhysicsSpace space = getPhysicsSpace(); + sbc.setPhysicsSpace(space); + hiddenObjects.addException(sbc); + } + + /** + * Add a rubber duck to the scene. + */ + private void addRubberDuck() { + Spatial cgModel = assetManager.loadModel("Models/Duck/Duck.j3o"); + rootNode.attachChild(cgModel); + + SoftBodyControl sbc = new SoftBodyControl(); + cgModel.addControl(sbc); + PhysicsSoftBody psb = sbc.getBody(); + + float totalMass = 1f; + psb.setMassByArea(totalMass); + + SoftBodyConfig config = psb.getSoftConfig(); + config.set(Sbcp.KineticHardness, 1f); + config.set(Sbcp.PoseMatching, 0.03f); + //config.setPositionIterations(15); + + boolean setVolumePose = false; + boolean setFramePose = true; + psb.setPose(setVolumePose, setFramePose); + + psb.applyRotation(new Quaternion().fromAngles(0.4f, 0f, 1f)); + psb.applyTranslation(new Vector3f(0f, 1.2f, 0f)); + + PhysicsSpace space = getPhysicsSpace(); + sbc.setPhysicsSpace(space); + hiddenObjects.addException(sbc); + } + + /** + * Clean up after a test. + */ + private void cleanupAfterTest() { + // Remove any scenery. Debug meshes are under a different root node. + rootNode.detachAllChildren(); + + // Remove physics objects, which also removes their debug meshes. + PhysicsSpace physicsSpace = getPhysicsSpace(); + physicsSpace.destroy(); + assert physicsSpace.isEmpty(); + + // Clear the hidden-object list. + hiddenObjects.clearExceptions(); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + float yDegrees = MyCamera.yDegrees(cam); + float aspectRatio = MyCamera.viewAspectRatio(cam); + float near = 0.02f; + float far = 20f; + cam.setFrustumPerspective(yDegrees, aspectRatio, near, far); + + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(2f); + flyCam.setZoomSpeed(2f); + + cam.setLocation(new Vector3f(-1.7f, 0.5f, 4.4f)); + cam.setRotation(new Quaternion(-0.0065f, 0.977669f, 0.0283f, 0.20814f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + CollisionShape.setDefaultMargin(0.005f); // 5-mm margin + + bulletAppState = new SoftPhysicsAppState(); + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugFilter(hiddenObjects); + bulletAppState.setDebugInitListener(this); + stateManager.attach(bulletAppState); + + setGravityAll(1f); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/Windlass.java b/MinieExamples/src/main/java/jme3utilities/minie/test/Windlass.java index 015e9db57..d20e08a01 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/Windlass.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/Windlass.java @@ -1,699 +1,699 @@ -/* - Copyright (c) 2022-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test; - -import com.jme3.app.state.AppState; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.AfMode; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.font.BitmapText; -import com.jme3.input.KeyInput; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyString; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.RectSizeLimits; -import jme3utilities.minie.ClassFilter; -import jme3utilities.minie.test.common.PhysicsDemo; -import jme3utilities.ui.CameraOrbitAppState; -import jme3utilities.ui.DisplaySettings; -import jme3utilities.ui.DsEditOverlay; -import jme3utilities.ui.InputMode; -import jme3utilities.ui.ShowDialog; -import jme3utilities.ui.Signals; - -/** - * A physics demo that simulates a cable coiled around a horizontal barrel. The - * cable is composed of capsule-shaped rigid segments. A hook is attached to the - * free end of the cable. - * - * @author Stephen Gold sgold@sonic.net - */ -public class Windlass - extends PhysicsDemo - implements DebugInitListener, PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * simulation time step (in seconds) - */ - final private static float timeStep = 0.0015f; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(Windlass.class.getName()); - /** - * application name (for the title bar of the app's window) - */ - final private static String applicationName - = Windlass.class.getSimpleName(); - // ************************************************************************* - // fields - - /** - * text displayed at the bottom of the GUI node - */ - private static BitmapText statusText; - /** - * AppState to manage the PhysicsSpace - */ - private static BulletAppState bulletAppState; - /** - * All cable segments have exactly the same shape. - */ - private static CollisionShape segmentShape; - /** - * proposed display settings, for the DsEditOverlay - */ - private static DisplaySettings proposedSettings; - /** - * rotation of the barrel (in radians) - */ - private static float barrelXRotation; - /** - * input signal: 1→turn counter-clockwise (initially lowers the hook) - */ - private static int signalCcw; - /** - * input signal: 1→turn clockwise (initially raises the hook) - */ - private static int signalCw; - /** - * body that represents the barrel - */ - private static PhysicsRigidBody barrel; - /** - * orientation of the barrel - */ - final private static Quaternion barrelOrientation = new Quaternion(); - /** - * location of the forward pivot in a segment's local coordinates - */ - final private static Vector3f localPivot = new Vector3f(); - // ************************************************************************* - // constructors - - /** - * Instantiate the Windlass application. - */ - public Windlass() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the Windlass application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - // Mute the chatty loggers in certain packages. - Heart.setLoggingLevels(Level.WARNING); - - for (String arg : arguments) { - switch (arg) { - case "--deleteOnly": - Heart.deleteStoredSettings(applicationName); - return; - default: - } - } - - Windlass application = new Windlass(); - RectSizeLimits sizeLimits = new RectSizeLimits( - 480, 240, // min width, height - 2_048, 1_080 // max width, height - ); - final String title = applicationName + " " + MyString.join(arguments); - proposedSettings = new DisplaySettings( - application, applicationName, sizeLimits) { - @Override - protected void applyOverrides(AppSettings settings) { - setShowDialog(ShowDialog.Never); - settings.setAudioRenderer(null); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - if (settings.getSamples() < 1) { - settings.setSamples(4); // anti-aliasing - } - settings.setResizable(true); - settings.setTitle(title); // Customize the window's title bar. - } - }; - - AppSettings appSettings = proposedSettings.initialize(); - if (appSettings != null) { - application.setSettings(appSettings); - /* - * If the settings dialog should be shown, - * it has already been shown by DisplaySettings.initialize(). - */ - application.setShowSettings(false); - - application.start(); - } - } - // ************************************************************************* - // PhysicsDemo methods - - /** - * Initialize the application. - */ - @Override - public void acorusInit() { - configureCamera(); - configureDumper(); - generateMaterials(); - configurePhysics(); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - // Add the status text to the GUI. - statusText = new BitmapText(guiFont); - statusText.setLocalTranslation(205f, 25f, 0f); - guiNode.attachChild(statusText); - - AppState dseOverlay = new DsEditOverlay(proposedSettings); - boolean success = stateManager.attach(dseOverlay); - assert success; - - super.acorusInit(); - - configureCamera(); - viewPort.setBackgroundColor(skyColor); - - float cableRadius = 1f; // should be much larger than collision margin - Vector3f attachPoint = addBarrel(cableRadius); - /* - * Determine the segment length, which is also the distance between - * successive pivots. - */ - int numSegmentsPerCoil = 12; - float deltaPhi = FastMath.TWO_PI / numSegmentsPerCoil; - float z0 = attachPoint.z; - float deltaX = 2.1f * cableRadius / numSegmentsPerCoil; - float deltaY = 2f * z0 * FastMath.tan(deltaPhi / 2f); - float segmentLength = MyMath.hypotenuse(deltaX, deltaY); - - // The segment shape is a Z-axis capsule. - assert segmentLength > 2f * cableRadius; // alternate segments collide! - segmentShape = new CapsuleCollisionShape( - cableRadius, segmentLength, PhysicsSpace.AXIS_Z); - localPivot.set(0f, 0f, segmentLength / 2f); - /* - * Make the first cable segment tangent to the +Z side of the barrel - * and attach it with a fixed joint (all DOFs locked). - */ - float zRotation = FastMath.atan2(deltaX, deltaY); - Quaternion orientation = new Quaternion().fromAngles(0f, zRotation, 0f); - new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f) - .mult(orientation, orientation); - - PhysicsRigidBody segment = addCableSegment(attachPoint, orientation); - New6Dof fixed = New6Dof.newInstance( - segment, barrel, attachPoint, orientation, RotationOrder.XYZ); - for (int axisIndex = 0; axisIndex < MyVector3f.numAxes; ++axisIndex) { - RotationMotor motor = fixed.getRotationMotor(axisIndex); - motor.set(MotorParam.LowerLimit, 0f); - motor.set(MotorParam.UpperLimit, 0f); - } - addJoint(fixed); - - Quaternion rotatePhi = new Quaternion().fromAngles(deltaPhi, 0f, 0f); - int numCoils = 4; - int numCoiledSegments = numCoils * numSegmentsPerCoil; - - // Attach successive segments a spiral coiling around the barrel. - float phi = FastMath.HALF_PI; - PhysicsRigidBody endSegment = segment; - Vector3f center = attachPoint.clone(); - for (int segmentI = 0; segmentI < numCoiledSegments; ++segmentI) { - // Calculate the position of the next segment. - center.x += deltaX; - phi += deltaPhi; - center.y = z0 * FastMath.cos(phi); - center.z = z0 * FastMath.sin(phi); - rotatePhi.mult(orientation, orientation); - - // Create a new segment and splice it to the existing cable. - PhysicsRigidBody newSegment = addCableSegment(center, orientation); - spliceCableSegments(newSegment, endSegment); - - endSegment = newSegment; - } - - orientation.fromAngles(FastMath.HALF_PI, 0f, 0f); - int numPendantSegments = 4; - - // Attach successive segments in vertical drop. - for (int segmentI = 0; segmentI < numPendantSegments; ++segmentI) { - // Calculate the location of the next segment. - center.y -= segmentLength; - - // Create a new segment and splice it to the existing cable. - PhysicsRigidBody newSegment = addCableSegment(center, orientation); - spliceCableSegments(newSegment, endSegment); - - endSegment = newSegment; - } - - addHook(endSegment, cableRadius); - } - - /** - * Initialize the library of named materials during startup. - */ - @Override - public void generateMaterials() { - ColorRGBA gray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Material shiny = MyAsset.createShinyMaterial(assetManager, gray); - shiny.setFloat("Shininess", 100f); - registerMaterial("shiny", shiny); - - ColorRGBA brown = new ColorRGBA(0.15f, 0.1f, 0f, 1f); - Material drab = MyAsset.createShadedMaterial(assetManager, brown); - registerMaterial("drab", drab); - } - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - @Override - protected BulletAppState getBulletAppState() { - assert bulletAppState != null; - return bulletAppState; - } - - /** - * Determine the length of debug axis arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - @Override - protected float maxArrowLength() { - return 8f; - } - - /** - * Add application-specific hotkey bindings (and override existing ones, if - * necessary). - */ - @Override - public void moreDefaultBindings() { - InputMode dim = getDefaultInputMode(); - - dim.bind(asDumpSpace, KeyInput.KEY_O); - dim.bind(asDumpViewport, KeyInput.KEY_P); - - dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); - - dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); - dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); - - dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); - dim.bind(asToggleHelp, KeyInput.KEY_H); - dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); - dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); - dim.bind(asToggleVArrows, KeyInput.KEY_K); - dim.bind(asToggleWArrows, KeyInput.KEY_N); - - dim.bindSignal("wind ccw", KeyInput.KEY_DOWN); - dim.bindSignal("wind cw", KeyInput.KEY_UP); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - Signals signals = getSignals(); - signalCcw = signals.test("wind ccw") ? 1 : 0; - signalCw = signals.test("wind cw") ? 1 : 0; - - updateStatusText(); - - } - // ************************************************************************* - // DebugInitListener methods - - /** - * Callback from BulletDebugAppState, invoked just before the debug scene is - * added to the debug viewports. - * - * @param physicsDebugRootNode the root node of the debug scene (not null) - */ - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Turn the barrel based on user-input signals. - float turnRate = 4f; // radians per second - barrelXRotation += (signalCcw - signalCw) * turnRate * timeStep; - barrelOrientation.fromAngles(barrelXRotation, 0f, 0f); - barrel.setPhysicsRotation(barrelOrientation); - } - // ************************************************************************* - // private methods - - /** - * Add the barrel, which is a kinematic rigid body shaped like a horizontal - * cylinder, with flanges and handles at both ends. - * - * @param cableRadius the radius of the cable (in physics-space units, - * >0) - * @return the attachment point for the cable (a location vector in physics - * space) - */ - private Vector3f addBarrel(float cableRadius) { - int axis = PhysicsSpace.AXIS_X; - - float drumLength = 12f * cableRadius; - float drumRadius = 0.6f * drumLength; - CollisionShape cylinderShape = new CylinderCollisionShape( - drumRadius, drumLength, axis); - - float flangeRadius = drumRadius + 3.5f * cableRadius; - float flangeWidth = 0.1f * drumLength; - CollisionShape flangeShape = new CylinderCollisionShape( - flangeRadius, flangeWidth, axis); - - float handleRadius = 0.8f * cableRadius; - float handleLength = 8f * cableRadius; - CollisionShape handleShape = new CylinderCollisionShape( - handleRadius, handleLength, axis); - - CompoundCollisionShape barrelShape = new CompoundCollisionShape(5); - barrelShape.addChildShape(cylinderShape); - - float flangeX = (drumLength + flangeWidth) / 2f; - barrelShape.addChildShape(flangeShape, +flangeX, 0f, 0f); - barrelShape.addChildShape(flangeShape, -flangeX, 0f, 0f); - - float handleX = drumLength / 2f + flangeWidth + handleLength / 2f; - float handleY = flangeRadius - handleRadius; - barrelShape.addChildShape(handleShape, +handleX, +handleY, 0f); - barrelShape.addChildShape(handleShape, -handleX, -handleY, 0f); - - float barrelMass = 100f; - barrel = new PhysicsRigidBody(barrelShape, barrelMass); - barrel.setKinematic(true); - barrel.setAnisotropicFriction( - new Vector3f(900f, 10f, 10f), AfMode.basic); - barrel.setFriction(0f); // disable normal friction - - Material material = findMaterial("drab"); - barrel.setDebugMaterial(material); - barrel.setDebugMeshNormals(MeshNormals.Smooth); - barrel.setDebugMeshResolution(DebugShapeFactory.highResolution); - - addCollisionObject(barrel); - - // Calculate an attachment point on the +Z side of the drum; - float x0 = -0.49f * drumLength + cableRadius; - float z0 = drumRadius + cableRadius; - Vector3f result = new Vector3f(x0, 0f, z0); - - return result; - } - - /** - * Add a single segment of cable. - * - * @param center the desired center location in physics space (not null, - * unaffected) - * @param orientation the desired orientation in physics space (not null, - * unaffected) - * @return a new instance - */ - private PhysicsRigidBody addCableSegment( - Vector3f center, Quaternion orientation) { - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(segmentShape, mass); - - result.setPhysicsLocation(center); - result.setPhysicsRotation(orientation); - - Material material = findMaterial("shiny"); - result.setDebugMaterial(material); - result.setDebugMeshNormals(MeshNormals.Smooth); - result.setDebugMeshResolution(DebugShapeFactory.highResolution); - - addCollisionObject(result); - - return result; - } - - /** - * Attach a hook to the end of the cable. - * - * @param endSegment the final segment of the cable (not null) - * @param cableRadius the radius of the cable (>0) - */ - private void addHook(PhysicsRigidBody endSegment, float cableRadius) { - // Locate the final pivot. - Transform endTransform = endSegment.getTransform(null); - Vector3f pivotLocation - = MyMath.transform(endTransform, localPivot, null); - /* - * Collision shape is composed of 11 overlapping 2-sphere shapes, - * arranged in a circular arc. - */ - int numChildren = 11; - int numSpheres = numChildren + 1; - float hookRadius = 4f * cableRadius; - float maxThick = 2.1f * cableRadius; // max thickness - float minThick = 0.5f * cableRadius; // min thickness - - float[] radius = new float[numSpheres]; - float[] y = new float[numSpheres]; - float[] z = new float[numSpheres]; - float xAngle = 0f; // in radians - for (int sphereI = 0; sphereI < numSpheres; ++sphereI) { - float p = sphereI / (float) (numSpheres - 1); // goes from 0 to 1 - float p3 = FastMath.pow(p, 3f); - float thickness = maxThick - p3 * (maxThick - minThick); - radius[sphereI] = thickness / 2f; - if (sphereI > 0) { - xAngle += radius[sphereI] / hookRadius; - } - y[sphereI] = hookRadius * FastMath.cos(xAngle); - z[sphereI] = -hookRadius * FastMath.sin(xAngle); - xAngle += radius[sphereI] / hookRadius; - } - - List centers = new ArrayList<>(2); - centers.add(new Vector3f()); - centers.add(new Vector3f()); - - List radii = new ArrayList<>(2); - radii.add(0f); - radii.add(0f); - - CompoundCollisionShape shape = new CompoundCollisionShape(numChildren); - for (int childI = 0; childI < numChildren; ++childI) { - centers.get(0).set(0f, y[childI], z[childI]); - radii.set(0, radius[childI]); - - int nextI = childI + 1; - centers.get(1).set(0f, y[nextI], z[nextI]); - radii.set(1, radius[nextI]); - - MultiSphere twoSphere = new MultiSphere(centers, radii); - shape.addChildShape(twoSphere); - } - - float hookMass = 3f; - PhysicsRigidBody hook = new PhysicsRigidBody(shape, hookMass); - hook.setAngularDamping(0.7f); - hook.setLinearDamping(0.4f); - - float pivotY = hookRadius + maxThick / 2f; - Vector3f center = pivotLocation.subtract(0f, pivotY, 0f); - hook.setPhysicsLocation(center); - - Material material = findMaterial("shiny"); - hook.setDebugMaterial(material); - hook.setDebugMeshNormals(MeshNormals.Smooth); - hook.setDebugMeshResolution(DebugShapeFactory.highResolution); - - addCollisionObject(hook); - - Quaternion orientation = endTransform.getRotation(); // alias - New6Dof joint = New6Dof.newInstance(hook, endSegment, - pivotLocation, orientation, RotationOrder.XYZ); - joint.setCollisionBetweenLinkedBodies(false); - addJoint(joint); - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param rootSpatial which scene (not null) - */ - private static void addLighting(Spatial rootSpatial) { - ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - rootSpatial.addLight(ambient); - ambient.setName("ambient"); - - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootSpatial.addLight(sun); - sun.setName("sun"); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setDragToRotate(true); - flyCam.setMoveSpeed(20f); - flyCam.setZoomSpeed(20f); - - cam.setLocation(new Vector3f(30f, 25f, 135f)); - cam.setRotation(new Quaternion(-0.02f, 0.975877f, -0.19204f, -0.1019f)); - - AppState orbitState - = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); - stateManager.attach(orbitState); - } - - /** - * Configure physics during startup. - */ - private void configurePhysics() { - bulletAppState = new BulletAppState(); - bulletAppState.setDebugEnabled(true); - - // Visualize only the rigid bodies, not the joints. - bulletAppState.setDebugFilter(new ClassFilter(PhysicsRigidBody.class)); - bulletAppState.setDebugInitListener(this); - - stateManager.attach(bulletAppState); - - PhysicsSpace space = getPhysicsSpace(); - space.addTickListener(this); - space.setAccuracy(timeStep); - space.setMaxSubSteps(99); // default=4 - setGravityAll(981f); // 1 psu = 1 cm - } - - /** - * Connect the specified cable segments with a New6Dof joint. - * - * @param newSegment the new cable segment (not null) - * @param endSegment the final segment of the cable so far (not null) - */ - private void spliceCableSegments( - PhysicsRigidBody newSegment, PhysicsRigidBody endSegment) { - // Position the pivot. - Transform endTransform = endSegment.getTransform(null); - Vector3f pivotLocation - = MyMath.transform(endTransform, localPivot, null); - - Quaternion pivotOrientation = endSegment.getPhysicsRotation(null); - New6Dof joint = New6Dof.newInstance(newSegment, endSegment, - pivotLocation, pivotOrientation, RotationOrder.XYZ); - joint.setCollisionBetweenLinkedBodies(false); - - RotationMotor zrMotor = joint.getRotationMotor(PhysicsSpace.AXIS_Z); - zrMotor.set(MotorParam.Damping, 0.25f / timeStep); - zrMotor.set(MotorParam.LowerLimit, 0f); - zrMotor.set(MotorParam.UpperLimit, 0f); - zrMotor.setSpringEnabled(true); - - addJoint(joint); - } - - /** - * Update the status text in the GUI. - */ - private void updateStatusText() { - String message = isPaused() ? " PAUSED" : ""; - statusText.setText(message); - } -} +/* + Copyright (c) 2022-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test; + +import com.jme3.app.state.AppState; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.AfMode; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyString; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.RectSizeLimits; +import jme3utilities.minie.ClassFilter; +import jme3utilities.minie.test.common.PhysicsDemo; +import jme3utilities.ui.CameraOrbitAppState; +import jme3utilities.ui.DisplaySettings; +import jme3utilities.ui.DsEditOverlay; +import jme3utilities.ui.InputMode; +import jme3utilities.ui.ShowDialog; +import jme3utilities.ui.Signals; + +/** + * A physics demo that simulates a cable coiled around a horizontal barrel. The + * cable is composed of capsule-shaped rigid segments. A hook is attached to the + * free end of the cable. + * + * @author Stephen Gold sgold@sonic.net + */ +public class Windlass + extends PhysicsDemo + implements DebugInitListener, PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * simulation time step (in seconds) + */ + final private static float timeStep = 0.0015f; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(Windlass.class.getName()); + /** + * application name (for the title bar of the app's window) + */ + final private static String applicationName + = Windlass.class.getSimpleName(); + // ************************************************************************* + // fields + + /** + * text displayed at the bottom of the GUI node + */ + private static BitmapText statusText; + /** + * AppState to manage the PhysicsSpace + */ + private static BulletAppState bulletAppState; + /** + * All cable segments have exactly the same shape. + */ + private static CollisionShape segmentShape; + /** + * proposed display settings, for the DsEditOverlay + */ + private static DisplaySettings proposedSettings; + /** + * rotation of the barrel (in radians) + */ + private static float barrelXRotation; + /** + * input signal: 1→turn counter-clockwise (initially lowers the hook) + */ + private static int signalCcw; + /** + * input signal: 1→turn clockwise (initially raises the hook) + */ + private static int signalCw; + /** + * body that represents the barrel + */ + private static PhysicsRigidBody barrel; + /** + * orientation of the barrel + */ + final private static Quaternion barrelOrientation = new Quaternion(); + /** + * location of the forward pivot in a segment's local coordinates + */ + final private static Vector3f localPivot = new Vector3f(); + // ************************************************************************* + // constructors + + /** + * Instantiate the Windlass application. + */ + public Windlass() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the Windlass application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + // Mute the chatty loggers in certain packages. + Heart.setLoggingLevels(Level.WARNING); + + for (String arg : arguments) { + switch (arg) { + case "--deleteOnly": + Heart.deleteStoredSettings(applicationName); + return; + default: + } + } + + Windlass application = new Windlass(); + RectSizeLimits sizeLimits = new RectSizeLimits( + 480, 240, // min width, height + 2_048, 1_080 // max width, height + ); + final String title = applicationName + " " + MyString.join(arguments); + proposedSettings = new DisplaySettings( + application, applicationName, sizeLimits) { + @Override + protected void applyOverrides(AppSettings settings) { + setShowDialog(ShowDialog.Never); + settings.setAudioRenderer(null); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + if (settings.getSamples() < 1) { + settings.setSamples(4); // anti-aliasing + } + settings.setResizable(true); + settings.setTitle(title); // Customize the window's title bar. + } + }; + + AppSettings appSettings = proposedSettings.initialize(); + if (appSettings != null) { + application.setSettings(appSettings); + /* + * If the settings dialog should be shown, + * it has already been shown by DisplaySettings.initialize(). + */ + application.setShowSettings(false); + + application.start(); + } + } + // ************************************************************************* + // PhysicsDemo methods + + /** + * Initialize the application. + */ + @Override + public void acorusInit() { + configureCamera(); + configureDumper(); + generateMaterials(); + configurePhysics(); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + // Add the status text to the GUI. + statusText = new BitmapText(guiFont); + statusText.setLocalTranslation(205f, 25f, 0f); + guiNode.attachChild(statusText); + + AppState dseOverlay = new DsEditOverlay(proposedSettings); + boolean success = stateManager.attach(dseOverlay); + assert success; + + super.acorusInit(); + + configureCamera(); + viewPort.setBackgroundColor(skyColor); + + float cableRadius = 1f; // should be much larger than collision margin + Vector3f attachPoint = addBarrel(cableRadius); + /* + * Determine the segment length, which is also the distance between + * successive pivots. + */ + int numSegmentsPerCoil = 12; + float deltaPhi = FastMath.TWO_PI / numSegmentsPerCoil; + float z0 = attachPoint.z; + float deltaX = 2.1f * cableRadius / numSegmentsPerCoil; + float deltaY = 2f * z0 * FastMath.tan(deltaPhi / 2f); + float segmentLength = MyMath.hypotenuse(deltaX, deltaY); + + // The segment shape is a Z-axis capsule. + assert segmentLength > 2f * cableRadius; // alternate segments collide! + segmentShape = new CapsuleCollisionShape( + cableRadius, segmentLength, PhysicsSpace.AXIS_Z); + localPivot.set(0f, 0f, segmentLength / 2f); + /* + * Make the first cable segment tangent to the +Z side of the barrel + * and attach it with a fixed joint (all DOFs locked). + */ + float zRotation = FastMath.atan2(deltaX, deltaY); + Quaternion orientation = new Quaternion().fromAngles(0f, zRotation, 0f); + new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f) + .mult(orientation, orientation); + + PhysicsRigidBody segment = addCableSegment(attachPoint, orientation); + New6Dof fixed = New6Dof.newInstance( + segment, barrel, attachPoint, orientation, RotationOrder.XYZ); + for (int axisIndex = 0; axisIndex < MyVector3f.numAxes; ++axisIndex) { + RotationMotor motor = fixed.getRotationMotor(axisIndex); + motor.set(MotorParam.LowerLimit, 0f); + motor.set(MotorParam.UpperLimit, 0f); + } + addJoint(fixed); + + Quaternion rotatePhi = new Quaternion().fromAngles(deltaPhi, 0f, 0f); + int numCoils = 4; + int numCoiledSegments = numCoils * numSegmentsPerCoil; + + // Attach successive segments a spiral coiling around the barrel. + float phi = FastMath.HALF_PI; + PhysicsRigidBody endSegment = segment; + Vector3f center = attachPoint.clone(); + for (int segmentI = 0; segmentI < numCoiledSegments; ++segmentI) { + // Calculate the position of the next segment. + center.x += deltaX; + phi += deltaPhi; + center.y = z0 * FastMath.cos(phi); + center.z = z0 * FastMath.sin(phi); + rotatePhi.mult(orientation, orientation); + + // Create a new segment and splice it to the existing cable. + PhysicsRigidBody newSegment = addCableSegment(center, orientation); + spliceCableSegments(newSegment, endSegment); + + endSegment = newSegment; + } + + orientation.fromAngles(FastMath.HALF_PI, 0f, 0f); + int numPendantSegments = 4; + + // Attach successive segments in vertical drop. + for (int segmentI = 0; segmentI < numPendantSegments; ++segmentI) { + // Calculate the location of the next segment. + center.y -= segmentLength; + + // Create a new segment and splice it to the existing cable. + PhysicsRigidBody newSegment = addCableSegment(center, orientation); + spliceCableSegments(newSegment, endSegment); + + endSegment = newSegment; + } + + addHook(endSegment, cableRadius); + } + + /** + * Initialize the library of named materials during startup. + */ + @Override + public void generateMaterials() { + ColorRGBA gray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Material shiny = MyAsset.createShinyMaterial(assetManager, gray); + shiny.setFloat("Shininess", 100f); + registerMaterial("shiny", shiny); + + ColorRGBA brown = new ColorRGBA(0.15f, 0.1f, 0f, 1f); + Material drab = MyAsset.createShadedMaterial(assetManager, brown); + registerMaterial("drab", drab); + } + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + @Override + protected BulletAppState getBulletAppState() { + assert bulletAppState != null; + return bulletAppState; + } + + /** + * Determine the length of debug axis arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + @Override + protected float maxArrowLength() { + return 8f; + } + + /** + * Add application-specific hotkey bindings (and override existing ones, if + * necessary). + */ + @Override + public void moreDefaultBindings() { + InputMode dim = getDefaultInputMode(); + + dim.bind(asDumpSpace, KeyInput.KEY_O); + dim.bind(asDumpViewport, KeyInput.KEY_P); + + dim.bind(asEditDisplaySettings, KeyInput.KEY_TAB); + + dim.bindSignal("orbitLeft", KeyInput.KEY_LEFT); + dim.bindSignal("orbitRight", KeyInput.KEY_RIGHT); + + dim.bind(asToggleAabbs, KeyInput.KEY_APOSTROPHE); + dim.bind(asToggleHelp, KeyInput.KEY_H); + dim.bind(asTogglePause, KeyInput.KEY_PAUSE, KeyInput.KEY_PERIOD); + dim.bind(asTogglePcoAxes, KeyInput.KEY_SEMICOLON); + dim.bind(asToggleVArrows, KeyInput.KEY_K); + dim.bind(asToggleWArrows, KeyInput.KEY_N); + + dim.bindSignal("wind ccw", KeyInput.KEY_DOWN); + dim.bindSignal("wind cw", KeyInput.KEY_UP); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + Signals signals = getSignals(); + signalCcw = signals.test("wind ccw") ? 1 : 0; + signalCw = signals.test("wind cw") ? 1 : 0; + + updateStatusText(); + + } + // ************************************************************************* + // DebugInitListener methods + + /** + * Callback from BulletDebugAppState, invoked just before the debug scene is + * added to the debug viewports. + * + * @param physicsDebugRootNode the root node of the debug scene (not null) + */ + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Turn the barrel based on user-input signals. + float turnRate = 4f; // radians per second + barrelXRotation += (signalCcw - signalCw) * turnRate * timeStep; + barrelOrientation.fromAngles(barrelXRotation, 0f, 0f); + barrel.setPhysicsRotation(barrelOrientation); + } + // ************************************************************************* + // private methods + + /** + * Add the barrel, which is a kinematic rigid body shaped like a horizontal + * cylinder, with flanges and handles at both ends. + * + * @param cableRadius the radius of the cable (in physics-space units, + * >0) + * @return the attachment point for the cable (a location vector in physics + * space) + */ + private Vector3f addBarrel(float cableRadius) { + int axis = PhysicsSpace.AXIS_X; + + float drumLength = 12f * cableRadius; + float drumRadius = 0.6f * drumLength; + CollisionShape cylinderShape = new CylinderCollisionShape( + drumRadius, drumLength, axis); + + float flangeRadius = drumRadius + 3.5f * cableRadius; + float flangeWidth = 0.1f * drumLength; + CollisionShape flangeShape = new CylinderCollisionShape( + flangeRadius, flangeWidth, axis); + + float handleRadius = 0.8f * cableRadius; + float handleLength = 8f * cableRadius; + CollisionShape handleShape = new CylinderCollisionShape( + handleRadius, handleLength, axis); + + CompoundCollisionShape barrelShape = new CompoundCollisionShape(5); + barrelShape.addChildShape(cylinderShape); + + float flangeX = (drumLength + flangeWidth) / 2f; + barrelShape.addChildShape(flangeShape, +flangeX, 0f, 0f); + barrelShape.addChildShape(flangeShape, -flangeX, 0f, 0f); + + float handleX = drumLength / 2f + flangeWidth + handleLength / 2f; + float handleY = flangeRadius - handleRadius; + barrelShape.addChildShape(handleShape, +handleX, +handleY, 0f); + barrelShape.addChildShape(handleShape, -handleX, -handleY, 0f); + + float barrelMass = 100f; + barrel = new PhysicsRigidBody(barrelShape, barrelMass); + barrel.setKinematic(true); + barrel.setAnisotropicFriction( + new Vector3f(900f, 10f, 10f), AfMode.basic); + barrel.setFriction(0f); // disable normal friction + + Material material = findMaterial("drab"); + barrel.setDebugMaterial(material); + barrel.setDebugMeshNormals(MeshNormals.Smooth); + barrel.setDebugMeshResolution(DebugShapeFactory.highResolution); + + addCollisionObject(barrel); + + // Calculate an attachment point on the +Z side of the drum; + float x0 = -0.49f * drumLength + cableRadius; + float z0 = drumRadius + cableRadius; + Vector3f result = new Vector3f(x0, 0f, z0); + + return result; + } + + /** + * Add a single segment of cable. + * + * @param center the desired center location in physics space (not null, + * unaffected) + * @param orientation the desired orientation in physics space (not null, + * unaffected) + * @return a new instance + */ + private PhysicsRigidBody addCableSegment( + Vector3f center, Quaternion orientation) { + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(segmentShape, mass); + + result.setPhysicsLocation(center); + result.setPhysicsRotation(orientation); + + Material material = findMaterial("shiny"); + result.setDebugMaterial(material); + result.setDebugMeshNormals(MeshNormals.Smooth); + result.setDebugMeshResolution(DebugShapeFactory.highResolution); + + addCollisionObject(result); + + return result; + } + + /** + * Attach a hook to the end of the cable. + * + * @param endSegment the final segment of the cable (not null) + * @param cableRadius the radius of the cable (>0) + */ + private void addHook(PhysicsRigidBody endSegment, float cableRadius) { + // Locate the final pivot. + Transform endTransform = endSegment.getTransform(null); + Vector3f pivotLocation + = MyMath.transform(endTransform, localPivot, null); + /* + * Collision shape is composed of 11 overlapping 2-sphere shapes, + * arranged in a circular arc. + */ + int numChildren = 11; + int numSpheres = numChildren + 1; + float hookRadius = 4f * cableRadius; + float maxThick = 2.1f * cableRadius; // max thickness + float minThick = 0.5f * cableRadius; // min thickness + + float[] radius = new float[numSpheres]; + float[] y = new float[numSpheres]; + float[] z = new float[numSpheres]; + float xAngle = 0f; // in radians + for (int sphereI = 0; sphereI < numSpheres; ++sphereI) { + float p = sphereI / (float) (numSpheres - 1); // goes from 0 to 1 + float p3 = FastMath.pow(p, 3f); + float thickness = maxThick - p3 * (maxThick - minThick); + radius[sphereI] = thickness / 2f; + if (sphereI > 0) { + xAngle += radius[sphereI] / hookRadius; + } + y[sphereI] = hookRadius * FastMath.cos(xAngle); + z[sphereI] = -hookRadius * FastMath.sin(xAngle); + xAngle += radius[sphereI] / hookRadius; + } + + List centers = new ArrayList<>(2); + centers.add(new Vector3f()); + centers.add(new Vector3f()); + + List radii = new ArrayList<>(2); + radii.add(0f); + radii.add(0f); + + CompoundCollisionShape shape = new CompoundCollisionShape(numChildren); + for (int childI = 0; childI < numChildren; ++childI) { + centers.get(0).set(0f, y[childI], z[childI]); + radii.set(0, radius[childI]); + + int nextI = childI + 1; + centers.get(1).set(0f, y[nextI], z[nextI]); + radii.set(1, radius[nextI]); + + MultiSphere twoSphere = new MultiSphere(centers, radii); + shape.addChildShape(twoSphere); + } + + float hookMass = 3f; + PhysicsRigidBody hook = new PhysicsRigidBody(shape, hookMass); + hook.setAngularDamping(0.7f); + hook.setLinearDamping(0.4f); + + float pivotY = hookRadius + maxThick / 2f; + Vector3f center = pivotLocation.subtract(0f, pivotY, 0f); + hook.setPhysicsLocation(center); + + Material material = findMaterial("shiny"); + hook.setDebugMaterial(material); + hook.setDebugMeshNormals(MeshNormals.Smooth); + hook.setDebugMeshResolution(DebugShapeFactory.highResolution); + + addCollisionObject(hook); + + Quaternion orientation = endTransform.getRotation(); // alias + New6Dof joint = New6Dof.newInstance(hook, endSegment, + pivotLocation, orientation, RotationOrder.XYZ); + joint.setCollisionBetweenLinkedBodies(false); + addJoint(joint); + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param rootSpatial which scene (not null) + */ + private static void addLighting(Spatial rootSpatial) { + ColorRGBA ambientColor = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + rootSpatial.addLight(ambient); + ambient.setName("ambient"); + + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootSpatial.addLight(sun); + sun.setName("sun"); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(20f); + flyCam.setZoomSpeed(20f); + + cam.setLocation(new Vector3f(30f, 25f, 135f)); + cam.setRotation(new Quaternion(-0.02f, 0.975877f, -0.19204f, -0.1019f)); + + AppState orbitState + = new CameraOrbitAppState(cam, "orbitLeft", "orbitRight"); + stateManager.attach(orbitState); + } + + /** + * Configure physics during startup. + */ + private void configurePhysics() { + bulletAppState = new BulletAppState(); + bulletAppState.setDebugEnabled(true); + + // Visualize only the rigid bodies, not the joints. + bulletAppState.setDebugFilter(new ClassFilter(PhysicsRigidBody.class)); + bulletAppState.setDebugInitListener(this); + + stateManager.attach(bulletAppState); + + PhysicsSpace space = getPhysicsSpace(); + space.addTickListener(this); + space.setAccuracy(timeStep); + space.setMaxSubSteps(99); // default=4 + setGravityAll(981f); // 1 psu = 1 cm + } + + /** + * Connect the specified cable segments with a New6Dof joint. + * + * @param newSegment the new cable segment (not null) + * @param endSegment the final segment of the cable so far (not null) + */ + private void spliceCableSegments( + PhysicsRigidBody newSegment, PhysicsRigidBody endSegment) { + // Position the pivot. + Transform endTransform = endSegment.getTransform(null); + Vector3f pivotLocation + = MyMath.transform(endTransform, localPivot, null); + + Quaternion pivotOrientation = endSegment.getPhysicsRotation(null); + New6Dof joint = New6Dof.newInstance(newSegment, endSegment, + pivotLocation, pivotOrientation, RotationOrder.XYZ); + joint.setCollisionBetweenLinkedBodies(false); + + RotationMotor zrMotor = joint.getRotationMotor(PhysicsSpace.AXIS_Z); + zrMotor.set(MotorParam.Damping, 0.25f / timeStep); + zrMotor.set(MotorParam.LowerLimit, 0f); + zrMotor.set(MotorParam.UpperLimit, 0f); + zrMotor.setSpringEnabled(true); + + addJoint(joint); + } + + /** + * Update the status text in the GUI. + */ + private void updateStatusText() { + String message = isPaused() ? " PAUSED" : ""; + statusText.setText(message); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/common/PhysicsDemo.java b/MinieExamples/src/main/java/jme3utilities/minie/test/common/PhysicsDemo.java index 9b16db819..5efa6c484 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/common/PhysicsDemo.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/common/PhysicsDemo.java @@ -1,923 +1,923 @@ -/* - Copyright (c) 2018-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.common; - -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.Box2dShape; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.material.Material; -import com.jme3.math.FastMath; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.shape.Box; -import com.jme3.texture.Texture; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.logging.Logger; -import jme3utilities.MeshNormals; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.mesh.Prism; -import jme3utilities.minie.DumpFlags; -import jme3utilities.minie.FilterAll; -import jme3utilities.minie.PhysicsDumper; -import jme3utilities.minie.test.shape.MinieTestShapes; -import jme3utilities.minie.test.shape.ShapeGenerator; -import jme3utilities.ui.AcorusDemo; - -/** - * An AcorusDemo with additional data and methods to test and/or demonstrate the - * capabilities of Minie. - * - * @author Stephen Gold sgold@sonic.net - */ -abstract public class PhysicsDemo extends AcorusDemo { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger loggerP - = Logger.getLogger(PhysicsDemo.class.getName()); - /** - * action string to dump the GUI scene graph - */ - final public static String asDumpGui = "dump gui"; - /** - * action string to dump the physics space - */ - final public static String asDumpSpace = "dump space"; - /** - * action string to dump the main scene graph - */ - final public static String asDumpScene = "dump scene"; - /** - * action string to dump the render manager (all viewports) - */ - final public static String asDumpScenes = "dump scenes"; - /** - * action string to dump the main viewport - */ - final public static String asDumpViewport = "dump viewport"; - /** - * action string to toggle debug visualization of collision-object bounding - * boxes - */ - final public static String asToggleAabbs = "toggle aabbs"; - /** - * action string to toggle debug visualization of CCD swept spheres - */ - final public static String asToggleCcdSpheres = "toggle ccdSpheres"; - /** - * action string to toggle debug visualization - */ - final public static String asToggleDebug = "toggle debug"; - /** - * action string to toggle debug visualization of gravity vectors - */ - final public static String asToggleGArrows = "toggle gArrows"; - /** - * action string to toggle debug visualization of collision-object axes - */ - final public static String asTogglePcoAxes = "toggle pcoAxes"; - /** - * action string to toggle debug visualization of velocity vectors - */ - final public static String asToggleVArrows = "toggle vArrows"; - /** - * action string to toggle debug visualization of angular-velocity vectors - */ - final public static String asToggleWArrows = "toggle wArrows"; - // ************************************************************************* - // fields - - /** - * filter to control visualization of axis-aligned bounding boxes - */ - private FilterAll aabbsFilter = null; - /** - * filter to control visualization of CCD swept spheres - */ - private FilterAll ccdSpheresFilter = null; - /** - * filter to control visualization of gravity vectors - */ - private FilterAll gArrowsFilter = null; - /** - * filter to control visualization of velocity vectors - */ - private FilterAll vArrowsFilter = null; - /** - * filter to control visualization of angular velocities - */ - private FilterAll wArrowsFilter = null; - /** - * library of named physics collision shapes - */ - final private Map namedShapes = new TreeMap<>(); - /** - * dump debugging information to {@code System.out} - */ - final private PhysicsDumper dumper = new PhysicsDumper(); - /** - * enhanced pseudo-random generator - */ - final private ShapeGenerator generator = new ShapeGenerator(); - // ************************************************************************* - // constructors - - /** - * Instantiate a generic PhysicsDemo. - */ - protected PhysicsDemo() { - } - // ************************************************************************* - // new methods exposed - - /** - * Activate all rigid bodies in the PhysicsSpace. - */ - public void activateAll() { - PhysicsSpace space = getPhysicsSpace(); - space.activateAll(true); - } - - /** - * Add the specified collision object to the PhysicsSpace. - * - * @param pco the object to add (not null, not in world) - */ - public void addCollisionObject(PhysicsCollisionObject pco) { - Validate.nonNull(pco, "pco"); - Validate.require(!pco.isInWorld(), "not in world"); - - PhysicsSpace space = getPhysicsSpace(); - space.addCollisionObject(pco); - - postAdd(pco); - } - - /** - * Add the specified PhysicsJoint to the PhysicsSpace. - * - * @param joint the joint to add (not null) - */ - public void addJoint(PhysicsJoint joint) { - Validate.nonNull(joint, "joint"); - - PhysicsSpace space = getPhysicsSpace(); - space.addJoint(joint); - } - - /** - * Configure the specified platform body and add it to the PhysicsSpace. - * - * @param body the body to add (not null, static or soft, not in world) - */ - public void addPlatform(PhysicsBody body) { - Validate.nonNull(body, "body"); - Validate.require(!body.isInWorld(), "not in world"); - Validate.require(body.isStatic() || body instanceof PhysicsSoftBody, - "static or soft"); - - Material material = findMaterial("platform"); - assert material != null; - body.setApplicationData(material); - body.setDebugMaterial(material); - - addCollisionObject(body); - } - - /** - * Add a platform to the PhysicsSpace. - * - * @param platformType the name of the desired platform type (not null) - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - */ - public void addPlatform(String platformType, float topY) { - Validate.nonNull(platformType, "platform type"); - - float topRadius = 20f; - float thickness = 0.2f * topRadius; - switch (platformType) { - case "box": - addBoxPlatform(topY, topRadius, thickness); - break; - - case "cone": - addConePlatform(topY, topRadius); - break; - - case "cylinder": - addCylinderPlatform(topY, topRadius); - break; - - case "hull": - addHullPlatform(topY, topRadius, thickness); - break; - - case "plane": - addPlanePlatform(topY); - break; - - case "roundedRectangle": - addRoundedRectangle(topY); - break; - - case "square": - addSquarePlatform(topY, topRadius, thickness); - break; - - case "triangle": - addPlatform(platformType, MeshNormals.Facet, topY); - break; - - default: - String message - = "platformType = " + MyString.quote(platformType); - throw new RuntimeException(message); - } - } - - /** - * Add a static rigid body with the named shape to the PhysicsSpace, to - * serve as a platform. - * - * @param shapeName (not null) - * @param normals which normals to include in the debug mesh - * @param centerY the desired Y coordinate of the center (in physics-space - * coordinates) - */ - public void addPlatform( - String shapeName, MeshNormals normals, float centerY) { - Validate.nonNull(shapeName, "shape name"); - - CollisionShape shape = findShape(shapeName); - assert shape != null : shapeName; - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(normals); - body.setPhysicsLocation(new Vector3f(0f, centerY, 0f)); - - addPlatform(body); - } - - /** - * Add a static cube to the scene, to serve as a platform. - * - * @param halfExtent half the desired extent of the cube (>0) - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - * @return the new physics control (not null) - */ - public RigidBodyControl attachCubePlatform(float halfExtent, float topY) { - Validate.positive(halfExtent, "half extent"); - - Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); - Geometry geometry = new Geometry("cube platform", mesh); - rootNode.attachChild(geometry); - - float centerY = topY - halfExtent; - geometry.move(0f, centerY, 0f); - Material platformMaterial = findMaterial("platform"); - assert platformMaterial != null; - geometry.setMaterial(platformMaterial); - geometry.setShadowMode(RenderQueue.ShadowMode.Receive); - - BoxCollisionShape shape = new BoxCollisionShape(halfExtent); - RigidBodyControl control - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - geometry.addControl(control); - control.setApplyScale(true); - control.setFriction(0.1f); - - PhysicsSpace space = getPhysicsSpace(); - control.setPhysicsSpace(space); - geometry.addControl(control); - - return control; - } - - /** - * Configure the PhysicsDumper. Invoke during startup. - */ - public void configureDumper() { - dumper.setEnabled(DumpFlags.ChildShapes, true); - dumper.setEnabled(DumpFlags.JointsInBodies, true); - dumper.setEnabled(DumpFlags.ShadowModes, true); - dumper.setEnabled(DumpFlags.Transforms, true); - } - - /** - * Describe the current physics debug options, assuming debug is enabled. - * - * @return a descriptive string of text (not null, not empty) - */ - public String describePhysicsDebugOptions() { - String result = "Physics"; - - if (aabbsFilter != null) { - result += "+AABBs"; - } - - if (ccdSpheresFilter != null) { - result += "+CcdSpheres"; - } - - if (gArrowsFilter != null) { - result += "+GArrows"; - } - - BulletAppState bulletAppState = getBulletAppState(); - if (bulletAppState.debugAxisLength() > 0f) { - result += "+PcoAxes"; - } - - if (vArrowsFilter != null) { - result += "+VArrows"; - } - - return result; - } - - /** - * Find the named CollisionShape in the library. - * - * @param name the name of the shape to find (not null) - * @return the pre-existing instance, or null if not found - */ - public CollisionShape findShape(String name) { - Validate.nonNull(name, "name"); - - CollisionShape result = namedShapes.get(name); - return result; - } - - /** - * Initialize the library of named collision shapes. Invoke during startup. - */ - public void generateShapes() { - MinieTestShapes.addShapes(namedShapes); - } - - /** - * Access the PhysicsDumper. - * - * @return the pre-existing instance (not null) - */ - public PhysicsDumper getDumper() { - return dumper; - } - - /** - * Access the ShapeGenerator (enhanced pseudo-random generator). - * - * @return the pre-existing instance (not null) - */ - public ShapeGenerator getGenerator() { - return generator; - } - - /** - * Access the current PhysicsSpace. - * - * @return the pre-existing instance (not null) - */ - public PhysicsSpace getPhysicsSpace() { - BulletAppState bas = getBulletAppState(); - PhysicsSpace result = bas.getPhysicsSpace(); - - assert result != null; - return result; - } - - /** - * Callback invoked after adding a collision object to the PhysicsSpace. - * Meant to be overridden. - * - * @param pco the object that was added (not null) - */ - public void postAdd(PhysicsCollisionObject pco) { - // do nothing - } - - /** - * Cast a physics ray from the mouse-cursor position and sort the hits by - * ascending hitFraction (in other words, nearest hit first). - * - * @return a new list of sorted results (not null) - */ - public List rayTestCursor() { - Vector2f screenXY = inputManager.getCursorPosition(); - Vector3f nearLocation - = cam.getWorldCoordinates(screenXY, MyCamera.nearZ); - Vector3f farLocation = cam.getWorldCoordinates(screenXY, MyCamera.farZ); - - PhysicsSpace space = getPhysicsSpace(); - List result - = space.rayTest(nearLocation, farLocation); - - return result; - } - - /** - * Add a CollisionShape to the library. - * - * @param name the key that will be used to find the shape (not null) - * @param shape (not null, alias created) - */ - public void registerShape(String name, CollisionShape shape) { - Validate.nonNull(name, "name"); - Validate.nonNull(shape, "shape"); - assert !namedShapes.containsKey(name); - - namedShapes.put(name, shape); - } - - /** - * Alter the damping fractions of all rigid bodies in the PhysicsSpace. - * - * @param damping the desired fraction (≥0, ≤1) - */ - public void setDampingAll(float damping) { - Validate.fraction(damping, "damping"); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - for (PhysicsRigidBody rigidBody : physicsSpace.getRigidBodyList()) { - rigidBody.setDamping(damping, damping); - } - } - - /** - * Alter the friction of all collision objects in the PhysicsSpace. - * - * @param friction the desired friction coefficient (≥0) - */ - public void setFrictionAll(float friction) { - Validate.nonNegative(friction, "friction"); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { - pco.setFriction(friction); - } - } - - /** - * Alter the gravity vectors of the PhysicsSpace and all bodies in it. - * - * @param gravity the desired magnitude (≥0) - */ - public void setGravityAll(float gravity) { - Validate.nonNegative(gravity, "gravity"); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - Vector3f gravityVector = new Vector3f(0f, -gravity, 0f); - physicsSpace.setGravity(gravityVector); - - for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { - if (pco instanceof PhysicsBody) { - PhysicsBody body = (PhysicsBody) pco; - body.setGravity(gravityVector); - } - } - } - - /** - * Alter the restitution of all collision objects in the PhysicsSpace. - * - * @param restitution the desired restitution fraction (≥0, ≤1) - */ - public void setRestitutionAll(float restitution) { - Validate.fraction(restitution, "restitution"); - - PhysicsSpace physicsSpace = getPhysicsSpace(); - for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { - pco.setRestitution(restitution); - } - } - - /** - * Toggle physics-debug visualization on/off. - */ - public void togglePhysicsDebug() { - BulletAppState bulletAppState = getBulletAppState(); - boolean enabled = bulletAppState.isDebugEnabled(); - bulletAppState.setDebugEnabled(!enabled); - } - - /** - * Remove a CollisionShape from the library, if it's been registered. - * - * @param key the key used to register the shape (not null) - */ - public void unregisterShape(String key) { - Validate.nonNull(key, "key"); - namedShapes.remove(key); - } - // ************************************************************************* - // new protected methods - - /** - * Access the active BulletAppState. - * - * @return the pre-existing instance (not null) - */ - abstract protected BulletAppState getBulletAppState(); - - /** - * Determine the length of debug axis arrows (when they're visible). - * - * @return the desired length (in physics-space units, ≥0) - */ - abstract protected float maxArrowLength(); - // ************************************************************************* - // AcorusDemo methods - - /** - * Initialize the library of named materials. Invoke during startup. - */ - @Override - public void generateMaterials() { - super.generateMaterials(); - - Texture texture = MyAsset.loadTexture( - assetManager, "Textures/greenTile.png", true); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - Material greenTile - = MyAsset.createShadedMaterial(assetManager, texture); - registerMaterial("greenTile", greenTile); - } - - /** - * Process an action that wasn't handled by the active InputMode. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - if (ongoing) { - switch (actionString) { - case asDumpGui: - dumper.dump(guiNode); - return; - case asDumpScene: - dumper.dump(rootNode); - return; - case asDumpScenes: - dumper.dump(renderManager); - return; - case asDumpSpace: - PhysicsSpace physicsSpace = getPhysicsSpace(); - dumper.dump(physicsSpace); - return; - case asDumpViewport: - dumper.dump(viewPort); - return; - - case asToggleAabbs: - toggleAabbs(); - return; - case asToggleCcdSpheres: - toggleCcdSpheres(); - return; - case asToggleDebug: - togglePhysicsDebug(); - return; - case asToggleGArrows: - toggleGravityArrows(); - return; - case asTogglePcoAxes: - togglePcoAxes(); - return; - case asToggleVArrows: - toggleVelocityArrows(); - return; - case asToggleWArrows: - toggleAngularVelocityArrows(); - return; - - default: - } - } - super.onAction(actionString, ongoing, tpf); - } - // ************************************************************************* - // private methods - - /** - * Add a large, static box to the PhysicsSpace, to serve as a platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - * @param topHalfExtent half the desired extent of the top surface (>0) - * @param thickness the desired thickness (in physics-space units, >0) - */ - private void addBoxPlatform( - float topY, float topHalfExtent, float thickness) { - Validate.positive(topHalfExtent, "top half extent"); - Validate.positive(thickness, "thickness"); - - CollisionShape shape = new BoxCollisionShape( - topHalfExtent, thickness / 2f, topHalfExtent); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(MeshNormals.Facet); - body.setPhysicsLocation(new Vector3f(0f, topY - thickness / 2f, 0f)); - - addPlatform(body); - } - - /** - * Add a large, downward-pointing, static cone to the PhysicsSpace, to serve - * as a platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - * @param topRadius the desired radius of the top surface (>0) - */ - private void addConePlatform(float topY, float topRadius) { - Validate.positive(topRadius, "top radius"); - - float height = 2f * topRadius; // tall to mitigate smoothed normals - ConeCollisionShape shape = new ConeCollisionShape(topRadius, height); - - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(MeshNormals.Smooth); - body.setDebugMeshResolution(DebugShapeFactory.highResolution); - body.setPhysicsLocation(new Vector3f(0f, topY - height / 2f, 0f)); - /* - * Rotate the cone 180 degrees around the X axis - * so that it points downward instead of upward. - */ - Quaternion orientation - = new Quaternion().fromAngles(FastMath.PI, 0f, 0f); - body.setPhysicsRotation(orientation); - - addPlatform(body); - } - - /** - * Add a large, static cylinder to the PhysicsSpace, to serve as a platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - * @param topRadius the desired radius of the top surface (>0) - */ - private void addCylinderPlatform(float topY, float topRadius) { - Validate.positive(topRadius, "top radius"); - - float height = 3f * topRadius; // tall to mitigate smoothed normals - CylinderCollisionShape shape = new CylinderCollisionShape( - topRadius, height, PhysicsSpace.AXIS_Y); - - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(MeshNormals.Smooth); - body.setDebugMeshResolution(DebugShapeFactory.highResolution); - body.setPhysicsLocation(new Vector3f(0f, topY - height / 2f, 0f)); - - addPlatform(body); - } - - /** - * Add a large, static pentagonal prism to the PhysicsSpace, to serve as a - * platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - * @param topRadius the desired radius of the top surface (>0) - * @param thickness the desired thickness (in physics-space units, >0) - */ - private void addHullPlatform(float topY, float topRadius, float thickness) { - Validate.positive(topRadius, "top radius"); - Validate.positive(thickness, "thickness"); - - boolean normals = false; - Mesh mesh = new Prism(5, topRadius, thickness, normals); - HullCollisionShape shape = new HullCollisionShape(mesh); - - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(MeshNormals.Facet); - body.setPhysicsLocation(new Vector3f(0f, topY - thickness / 2f, 0f)); - - addPlatform(body); - } - - /** - * Add a static plane to the PhysicsSpace, to serve as a platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - */ - private void addPlanePlatform(float topY) { - Plane plane = new Plane(Vector3f.UNIT_Y, topY); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - float sideLength = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(sideLength); - body.setDebugMeshInitListener(planeDmiListener); - - body.setDebugMeshNormals(MeshNormals.Facet); - - addPlatform(body); - - Material material = findMaterial("greenTile"); - body.setApplicationData(material); - body.setDebugMaterial(material); - } - - /** - * Add a rounded rectangle to the PhysicsSpace, to serve as a platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - */ - private void addRoundedRectangle(float topY) { - CollisionShape shape = findShape("roundedRectangle"); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(MeshNormals.Facet); - Quaternion rotation - = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); - body.setPhysicsRotation(rotation); - body.setPhysicsLocation(new Vector3f(0f, topY, 0f)); - - addPlatform(body); - } - - /** - * Add a large, static 2-D square to the PhysicsSpace, to serve as a - * platform. - * - * @param topY the desired Y coordinate of the top surface (in physics-space - * coordinates) - * @param topHalfExtent half the desired extent of the top surface (>0) - * @param thickness the desired thickness (in physics-space units, >0) - */ - private void addSquarePlatform( - float topY, float topHalfExtent, float thickness) { - Validate.positive(topHalfExtent, "top half extent"); - Validate.positive(thickness, "thickness"); - - CollisionShape shape = new Box2dShape(topHalfExtent); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - body.setDebugMeshNormals(MeshNormals.Facet); - body.setPhysicsLocation(new Vector3f(0f, topY, 0f)); - Quaternion rotation - = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); - body.setPhysicsRotation(rotation); - body.setPhysicsLocation(new Vector3f(0f, topY, 0f)); - - addPlatform(body); - } - - /** - * Toggle visualization of collision-object bounding boxes. - */ - private void toggleAabbs() { - if (aabbsFilter == null) { - aabbsFilter = new FilterAll(true); - } else { - aabbsFilter = null; - } - - BulletAppState bulletAppState = getBulletAppState(); - bulletAppState.setDebugBoundingBoxFilter(aabbsFilter); - } - - /** - * Toggle visualization of rigid-body angular velocities. - */ - private void toggleAngularVelocityArrows() { - if (wArrowsFilter == null) { - wArrowsFilter = new FilterAll(true); - } else { - wArrowsFilter = null; - } - - BulletAppState bulletAppState = getBulletAppState(); - bulletAppState.setDebugAngularVelocityFilter(wArrowsFilter); - } - - /** - * Toggle visualization of CCD swept spheres. - */ - private void toggleCcdSpheres() { - if (ccdSpheresFilter == null) { - ccdSpheresFilter = new FilterAll(true); - } else { - ccdSpheresFilter = null; - } - - BulletAppState bulletAppState = getBulletAppState(); - bulletAppState.setDebugSweptSphereFilter(ccdSpheresFilter); - } - - /** - * Toggle visualization of body gravities. - */ - private void toggleGravityArrows() { - if (gArrowsFilter == null) { - gArrowsFilter = new FilterAll(true); - } else { - gArrowsFilter = null; - } - - BulletAppState bulletAppState = getBulletAppState(); - bulletAppState.setDebugGravityVectorFilter(gArrowsFilter); - } - - /** - * Toggle visualization of collision-object axes. - */ - private void togglePcoAxes() { - BulletAppState bulletAppState = getBulletAppState(); - - float axisLength = bulletAppState.debugAxisLength(); - if (axisLength != 0f) { - axisLength = 0f; - } else { - axisLength = maxArrowLength(); - } - bulletAppState.setDebugAxisLength(axisLength); - } - - /** - * Toggle visualization of rigid-body velocities. - */ - private void toggleVelocityArrows() { - if (vArrowsFilter == null) { - vArrowsFilter = new FilterAll(true); - } else { - vArrowsFilter = null; - } - - BulletAppState bulletAppState = getBulletAppState(); - bulletAppState.setDebugVelocityVectorFilter(vArrowsFilter); - } -} +/* + Copyright (c) 2018-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.common; + +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.Box2dShape; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.ConeCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.Box; +import com.jme3.texture.Texture; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Logger; +import jme3utilities.MeshNormals; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.mesh.Prism; +import jme3utilities.minie.DumpFlags; +import jme3utilities.minie.FilterAll; +import jme3utilities.minie.PhysicsDumper; +import jme3utilities.minie.test.shape.MinieTestShapes; +import jme3utilities.minie.test.shape.ShapeGenerator; +import jme3utilities.ui.AcorusDemo; + +/** + * An AcorusDemo with additional data and methods to test and/or demonstrate the + * capabilities of Minie. + * + * @author Stephen Gold sgold@sonic.net + */ +abstract public class PhysicsDemo extends AcorusDemo { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger loggerP + = Logger.getLogger(PhysicsDemo.class.getName()); + /** + * action string to dump the GUI scene graph + */ + final public static String asDumpGui = "dump gui"; + /** + * action string to dump the physics space + */ + final public static String asDumpSpace = "dump space"; + /** + * action string to dump the main scene graph + */ + final public static String asDumpScene = "dump scene"; + /** + * action string to dump the render manager (all viewports) + */ + final public static String asDumpScenes = "dump scenes"; + /** + * action string to dump the main viewport + */ + final public static String asDumpViewport = "dump viewport"; + /** + * action string to toggle debug visualization of collision-object bounding + * boxes + */ + final public static String asToggleAabbs = "toggle aabbs"; + /** + * action string to toggle debug visualization of CCD swept spheres + */ + final public static String asToggleCcdSpheres = "toggle ccdSpheres"; + /** + * action string to toggle debug visualization + */ + final public static String asToggleDebug = "toggle debug"; + /** + * action string to toggle debug visualization of gravity vectors + */ + final public static String asToggleGArrows = "toggle gArrows"; + /** + * action string to toggle debug visualization of collision-object axes + */ + final public static String asTogglePcoAxes = "toggle pcoAxes"; + /** + * action string to toggle debug visualization of velocity vectors + */ + final public static String asToggleVArrows = "toggle vArrows"; + /** + * action string to toggle debug visualization of angular-velocity vectors + */ + final public static String asToggleWArrows = "toggle wArrows"; + // ************************************************************************* + // fields + + /** + * filter to control visualization of axis-aligned bounding boxes + */ + private FilterAll aabbsFilter = null; + /** + * filter to control visualization of CCD swept spheres + */ + private FilterAll ccdSpheresFilter = null; + /** + * filter to control visualization of gravity vectors + */ + private FilterAll gArrowsFilter = null; + /** + * filter to control visualization of velocity vectors + */ + private FilterAll vArrowsFilter = null; + /** + * filter to control visualization of angular velocities + */ + private FilterAll wArrowsFilter = null; + /** + * library of named physics collision shapes + */ + final private Map namedShapes = new TreeMap<>(); + /** + * dump debugging information to {@code System.out} + */ + final private PhysicsDumper dumper = new PhysicsDumper(); + /** + * enhanced pseudo-random generator + */ + final private ShapeGenerator generator = new ShapeGenerator(); + // ************************************************************************* + // constructors + + /** + * Instantiate a generic PhysicsDemo. + */ + protected PhysicsDemo() { + } + // ************************************************************************* + // new methods exposed + + /** + * Activate all rigid bodies in the PhysicsSpace. + */ + public void activateAll() { + PhysicsSpace space = getPhysicsSpace(); + space.activateAll(true); + } + + /** + * Add the specified collision object to the PhysicsSpace. + * + * @param pco the object to add (not null, not in world) + */ + public void addCollisionObject(PhysicsCollisionObject pco) { + Validate.nonNull(pco, "pco"); + Validate.require(!pco.isInWorld(), "not in world"); + + PhysicsSpace space = getPhysicsSpace(); + space.addCollisionObject(pco); + + postAdd(pco); + } + + /** + * Add the specified PhysicsJoint to the PhysicsSpace. + * + * @param joint the joint to add (not null) + */ + public void addJoint(PhysicsJoint joint) { + Validate.nonNull(joint, "joint"); + + PhysicsSpace space = getPhysicsSpace(); + space.addJoint(joint); + } + + /** + * Configure the specified platform body and add it to the PhysicsSpace. + * + * @param body the body to add (not null, static or soft, not in world) + */ + public void addPlatform(PhysicsBody body) { + Validate.nonNull(body, "body"); + Validate.require(!body.isInWorld(), "not in world"); + Validate.require(body.isStatic() || body instanceof PhysicsSoftBody, + "static or soft"); + + Material material = findMaterial("platform"); + assert material != null; + body.setApplicationData(material); + body.setDebugMaterial(material); + + addCollisionObject(body); + } + + /** + * Add a platform to the PhysicsSpace. + * + * @param platformType the name of the desired platform type (not null) + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + */ + public void addPlatform(String platformType, float topY) { + Validate.nonNull(platformType, "platform type"); + + float topRadius = 20f; + float thickness = 0.2f * topRadius; + switch (platformType) { + case "box": + addBoxPlatform(topY, topRadius, thickness); + break; + + case "cone": + addConePlatform(topY, topRadius); + break; + + case "cylinder": + addCylinderPlatform(topY, topRadius); + break; + + case "hull": + addHullPlatform(topY, topRadius, thickness); + break; + + case "plane": + addPlanePlatform(topY); + break; + + case "roundedRectangle": + addRoundedRectangle(topY); + break; + + case "square": + addSquarePlatform(topY, topRadius, thickness); + break; + + case "triangle": + addPlatform(platformType, MeshNormals.Facet, topY); + break; + + default: + String message + = "platformType = " + MyString.quote(platformType); + throw new RuntimeException(message); + } + } + + /** + * Add a static rigid body with the named shape to the PhysicsSpace, to + * serve as a platform. + * + * @param shapeName (not null) + * @param normals which normals to include in the debug mesh + * @param centerY the desired Y coordinate of the center (in physics-space + * coordinates) + */ + public void addPlatform( + String shapeName, MeshNormals normals, float centerY) { + Validate.nonNull(shapeName, "shape name"); + + CollisionShape shape = findShape(shapeName); + assert shape != null : shapeName; + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(normals); + body.setPhysicsLocation(new Vector3f(0f, centerY, 0f)); + + addPlatform(body); + } + + /** + * Add a static cube to the scene, to serve as a platform. + * + * @param halfExtent half the desired extent of the cube (>0) + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + * @return the new physics control (not null) + */ + public RigidBodyControl attachCubePlatform(float halfExtent, float topY) { + Validate.positive(halfExtent, "half extent"); + + Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); + Geometry geometry = new Geometry("cube platform", mesh); + rootNode.attachChild(geometry); + + float centerY = topY - halfExtent; + geometry.move(0f, centerY, 0f); + Material platformMaterial = findMaterial("platform"); + assert platformMaterial != null; + geometry.setMaterial(platformMaterial); + geometry.setShadowMode(RenderQueue.ShadowMode.Receive); + + BoxCollisionShape shape = new BoxCollisionShape(halfExtent); + RigidBodyControl control + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + geometry.addControl(control); + control.setApplyScale(true); + control.setFriction(0.1f); + + PhysicsSpace space = getPhysicsSpace(); + control.setPhysicsSpace(space); + geometry.addControl(control); + + return control; + } + + /** + * Configure the PhysicsDumper. Invoke during startup. + */ + public void configureDumper() { + dumper.setEnabled(DumpFlags.ChildShapes, true); + dumper.setEnabled(DumpFlags.JointsInBodies, true); + dumper.setEnabled(DumpFlags.ShadowModes, true); + dumper.setEnabled(DumpFlags.Transforms, true); + } + + /** + * Describe the current physics debug options, assuming debug is enabled. + * + * @return a descriptive string of text (not null, not empty) + */ + public String describePhysicsDebugOptions() { + String result = "Physics"; + + if (aabbsFilter != null) { + result += "+AABBs"; + } + + if (ccdSpheresFilter != null) { + result += "+CcdSpheres"; + } + + if (gArrowsFilter != null) { + result += "+GArrows"; + } + + BulletAppState bulletAppState = getBulletAppState(); + if (bulletAppState.debugAxisLength() > 0f) { + result += "+PcoAxes"; + } + + if (vArrowsFilter != null) { + result += "+VArrows"; + } + + return result; + } + + /** + * Find the named CollisionShape in the library. + * + * @param name the name of the shape to find (not null) + * @return the pre-existing instance, or null if not found + */ + public CollisionShape findShape(String name) { + Validate.nonNull(name, "name"); + + CollisionShape result = namedShapes.get(name); + return result; + } + + /** + * Initialize the library of named collision shapes. Invoke during startup. + */ + public void generateShapes() { + MinieTestShapes.addShapes(namedShapes); + } + + /** + * Access the PhysicsDumper. + * + * @return the pre-existing instance (not null) + */ + public PhysicsDumper getDumper() { + return dumper; + } + + /** + * Access the ShapeGenerator (enhanced pseudo-random generator). + * + * @return the pre-existing instance (not null) + */ + public ShapeGenerator getGenerator() { + return generator; + } + + /** + * Access the current PhysicsSpace. + * + * @return the pre-existing instance (not null) + */ + public PhysicsSpace getPhysicsSpace() { + BulletAppState bas = getBulletAppState(); + PhysicsSpace result = bas.getPhysicsSpace(); + + assert result != null; + return result; + } + + /** + * Callback invoked after adding a collision object to the PhysicsSpace. + * Meant to be overridden. + * + * @param pco the object that was added (not null) + */ + public void postAdd(PhysicsCollisionObject pco) { + // do nothing + } + + /** + * Cast a physics ray from the mouse-cursor position and sort the hits by + * ascending hitFraction (in other words, nearest hit first). + * + * @return a new list of sorted results (not null) + */ + public List rayTestCursor() { + Vector2f screenXY = inputManager.getCursorPosition(); + Vector3f nearLocation + = cam.getWorldCoordinates(screenXY, MyCamera.nearZ); + Vector3f farLocation = cam.getWorldCoordinates(screenXY, MyCamera.farZ); + + PhysicsSpace space = getPhysicsSpace(); + List result + = space.rayTest(nearLocation, farLocation); + + return result; + } + + /** + * Add a CollisionShape to the library. + * + * @param name the key that will be used to find the shape (not null) + * @param shape (not null, alias created) + */ + public void registerShape(String name, CollisionShape shape) { + Validate.nonNull(name, "name"); + Validate.nonNull(shape, "shape"); + assert !namedShapes.containsKey(name); + + namedShapes.put(name, shape); + } + + /** + * Alter the damping fractions of all rigid bodies in the PhysicsSpace. + * + * @param damping the desired fraction (≥0, ≤1) + */ + public void setDampingAll(float damping) { + Validate.fraction(damping, "damping"); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + for (PhysicsRigidBody rigidBody : physicsSpace.getRigidBodyList()) { + rigidBody.setDamping(damping, damping); + } + } + + /** + * Alter the friction of all collision objects in the PhysicsSpace. + * + * @param friction the desired friction coefficient (≥0) + */ + public void setFrictionAll(float friction) { + Validate.nonNegative(friction, "friction"); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { + pco.setFriction(friction); + } + } + + /** + * Alter the gravity vectors of the PhysicsSpace and all bodies in it. + * + * @param gravity the desired magnitude (≥0) + */ + public void setGravityAll(float gravity) { + Validate.nonNegative(gravity, "gravity"); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + Vector3f gravityVector = new Vector3f(0f, -gravity, 0f); + physicsSpace.setGravity(gravityVector); + + for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { + if (pco instanceof PhysicsBody) { + PhysicsBody body = (PhysicsBody) pco; + body.setGravity(gravityVector); + } + } + } + + /** + * Alter the restitution of all collision objects in the PhysicsSpace. + * + * @param restitution the desired restitution fraction (≥0, ≤1) + */ + public void setRestitutionAll(float restitution) { + Validate.fraction(restitution, "restitution"); + + PhysicsSpace physicsSpace = getPhysicsSpace(); + for (PhysicsCollisionObject pco : physicsSpace.getPcoList()) { + pco.setRestitution(restitution); + } + } + + /** + * Toggle physics-debug visualization on/off. + */ + public void togglePhysicsDebug() { + BulletAppState bulletAppState = getBulletAppState(); + boolean enabled = bulletAppState.isDebugEnabled(); + bulletAppState.setDebugEnabled(!enabled); + } + + /** + * Remove a CollisionShape from the library, if it's been registered. + * + * @param key the key used to register the shape (not null) + */ + public void unregisterShape(String key) { + Validate.nonNull(key, "key"); + namedShapes.remove(key); + } + // ************************************************************************* + // new protected methods + + /** + * Access the active BulletAppState. + * + * @return the pre-existing instance (not null) + */ + abstract protected BulletAppState getBulletAppState(); + + /** + * Determine the length of debug axis arrows (when they're visible). + * + * @return the desired length (in physics-space units, ≥0) + */ + abstract protected float maxArrowLength(); + // ************************************************************************* + // AcorusDemo methods + + /** + * Initialize the library of named materials. Invoke during startup. + */ + @Override + public void generateMaterials() { + super.generateMaterials(); + + Texture texture = MyAsset.loadTexture( + assetManager, "Textures/greenTile.png", true); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + Material greenTile + = MyAsset.createShadedMaterial(assetManager, texture); + registerMaterial("greenTile", greenTile); + } + + /** + * Process an action that wasn't handled by the active InputMode. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + if (ongoing) { + switch (actionString) { + case asDumpGui: + dumper.dump(guiNode); + return; + case asDumpScene: + dumper.dump(rootNode); + return; + case asDumpScenes: + dumper.dump(renderManager); + return; + case asDumpSpace: + PhysicsSpace physicsSpace = getPhysicsSpace(); + dumper.dump(physicsSpace); + return; + case asDumpViewport: + dumper.dump(viewPort); + return; + + case asToggleAabbs: + toggleAabbs(); + return; + case asToggleCcdSpheres: + toggleCcdSpheres(); + return; + case asToggleDebug: + togglePhysicsDebug(); + return; + case asToggleGArrows: + toggleGravityArrows(); + return; + case asTogglePcoAxes: + togglePcoAxes(); + return; + case asToggleVArrows: + toggleVelocityArrows(); + return; + case asToggleWArrows: + toggleAngularVelocityArrows(); + return; + + default: + } + } + super.onAction(actionString, ongoing, tpf); + } + // ************************************************************************* + // private methods + + /** + * Add a large, static box to the PhysicsSpace, to serve as a platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + * @param topHalfExtent half the desired extent of the top surface (>0) + * @param thickness the desired thickness (in physics-space units, >0) + */ + private void addBoxPlatform( + float topY, float topHalfExtent, float thickness) { + Validate.positive(topHalfExtent, "top half extent"); + Validate.positive(thickness, "thickness"); + + CollisionShape shape = new BoxCollisionShape( + topHalfExtent, thickness / 2f, topHalfExtent); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(MeshNormals.Facet); + body.setPhysicsLocation(new Vector3f(0f, topY - thickness / 2f, 0f)); + + addPlatform(body); + } + + /** + * Add a large, downward-pointing, static cone to the PhysicsSpace, to serve + * as a platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + * @param topRadius the desired radius of the top surface (>0) + */ + private void addConePlatform(float topY, float topRadius) { + Validate.positive(topRadius, "top radius"); + + float height = 2f * topRadius; // tall to mitigate smoothed normals + ConeCollisionShape shape = new ConeCollisionShape(topRadius, height); + + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(MeshNormals.Smooth); + body.setDebugMeshResolution(DebugShapeFactory.highResolution); + body.setPhysicsLocation(new Vector3f(0f, topY - height / 2f, 0f)); + /* + * Rotate the cone 180 degrees around the X axis + * so that it points downward instead of upward. + */ + Quaternion orientation + = new Quaternion().fromAngles(FastMath.PI, 0f, 0f); + body.setPhysicsRotation(orientation); + + addPlatform(body); + } + + /** + * Add a large, static cylinder to the PhysicsSpace, to serve as a platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + * @param topRadius the desired radius of the top surface (>0) + */ + private void addCylinderPlatform(float topY, float topRadius) { + Validate.positive(topRadius, "top radius"); + + float height = 3f * topRadius; // tall to mitigate smoothed normals + CylinderCollisionShape shape = new CylinderCollisionShape( + topRadius, height, PhysicsSpace.AXIS_Y); + + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(MeshNormals.Smooth); + body.setDebugMeshResolution(DebugShapeFactory.highResolution); + body.setPhysicsLocation(new Vector3f(0f, topY - height / 2f, 0f)); + + addPlatform(body); + } + + /** + * Add a large, static pentagonal prism to the PhysicsSpace, to serve as a + * platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + * @param topRadius the desired radius of the top surface (>0) + * @param thickness the desired thickness (in physics-space units, >0) + */ + private void addHullPlatform(float topY, float topRadius, float thickness) { + Validate.positive(topRadius, "top radius"); + Validate.positive(thickness, "thickness"); + + boolean normals = false; + Mesh mesh = new Prism(5, topRadius, thickness, normals); + HullCollisionShape shape = new HullCollisionShape(mesh); + + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(MeshNormals.Facet); + body.setPhysicsLocation(new Vector3f(0f, topY - thickness / 2f, 0f)); + + addPlatform(body); + } + + /** + * Add a static plane to the PhysicsSpace, to serve as a platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + */ + private void addPlanePlatform(float topY) { + Plane plane = new Plane(Vector3f.UNIT_Y, topY); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + float sideLength = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(sideLength); + body.setDebugMeshInitListener(planeDmiListener); + + body.setDebugMeshNormals(MeshNormals.Facet); + + addPlatform(body); + + Material material = findMaterial("greenTile"); + body.setApplicationData(material); + body.setDebugMaterial(material); + } + + /** + * Add a rounded rectangle to the PhysicsSpace, to serve as a platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + */ + private void addRoundedRectangle(float topY) { + CollisionShape shape = findShape("roundedRectangle"); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(MeshNormals.Facet); + Quaternion rotation + = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); + body.setPhysicsRotation(rotation); + body.setPhysicsLocation(new Vector3f(0f, topY, 0f)); + + addPlatform(body); + } + + /** + * Add a large, static 2-D square to the PhysicsSpace, to serve as a + * platform. + * + * @param topY the desired Y coordinate of the top surface (in physics-space + * coordinates) + * @param topHalfExtent half the desired extent of the top surface (>0) + * @param thickness the desired thickness (in physics-space units, >0) + */ + private void addSquarePlatform( + float topY, float topHalfExtent, float thickness) { + Validate.positive(topHalfExtent, "top half extent"); + Validate.positive(thickness, "thickness"); + + CollisionShape shape = new Box2dShape(topHalfExtent); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + body.setDebugMeshNormals(MeshNormals.Facet); + body.setPhysicsLocation(new Vector3f(0f, topY, 0f)); + Quaternion rotation + = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); + body.setPhysicsRotation(rotation); + body.setPhysicsLocation(new Vector3f(0f, topY, 0f)); + + addPlatform(body); + } + + /** + * Toggle visualization of collision-object bounding boxes. + */ + private void toggleAabbs() { + if (aabbsFilter == null) { + aabbsFilter = new FilterAll(true); + } else { + aabbsFilter = null; + } + + BulletAppState bulletAppState = getBulletAppState(); + bulletAppState.setDebugBoundingBoxFilter(aabbsFilter); + } + + /** + * Toggle visualization of rigid-body angular velocities. + */ + private void toggleAngularVelocityArrows() { + if (wArrowsFilter == null) { + wArrowsFilter = new FilterAll(true); + } else { + wArrowsFilter = null; + } + + BulletAppState bulletAppState = getBulletAppState(); + bulletAppState.setDebugAngularVelocityFilter(wArrowsFilter); + } + + /** + * Toggle visualization of CCD swept spheres. + */ + private void toggleCcdSpheres() { + if (ccdSpheresFilter == null) { + ccdSpheresFilter = new FilterAll(true); + } else { + ccdSpheresFilter = null; + } + + BulletAppState bulletAppState = getBulletAppState(); + bulletAppState.setDebugSweptSphereFilter(ccdSpheresFilter); + } + + /** + * Toggle visualization of body gravities. + */ + private void toggleGravityArrows() { + if (gArrowsFilter == null) { + gArrowsFilter = new FilterAll(true); + } else { + gArrowsFilter = null; + } + + BulletAppState bulletAppState = getBulletAppState(); + bulletAppState.setDebugGravityVectorFilter(gArrowsFilter); + } + + /** + * Toggle visualization of collision-object axes. + */ + private void togglePcoAxes() { + BulletAppState bulletAppState = getBulletAppState(); + + float axisLength = bulletAppState.debugAxisLength(); + if (axisLength != 0f) { + axisLength = 0f; + } else { + axisLength = maxArrowLength(); + } + bulletAppState.setDebugAxisLength(axisLength); + } + + /** + * Toggle visualization of rigid-body velocities. + */ + private void toggleVelocityArrows() { + if (vArrowsFilter == null) { + vArrowsFilter = new FilterAll(true); + } else { + vArrowsFilter = null; + } + + BulletAppState bulletAppState = getBulletAppState(); + bulletAppState.setDebugVelocityVectorFilter(vArrowsFilter); + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/controllers/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/controllers/package-info.java index 2d04e0541..d1c38e3df 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/controllers/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/controllers/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2019, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Inverse kinematics (IK) controllers used in DynamicAnimControl demos. - */ -package jme3utilities.minie.test.controllers; +/* + Copyright (c) 2019, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Inverse kinematics (IK) controllers used in DynamicAnimControl demos. + */ +package jme3utilities.minie.test.controllers; diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue13.java b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue13.java index e9f2f3fbf..6fc009077 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue13.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue13.java @@ -1,213 +1,213 @@ -/* - Copyright (c) 2021-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.issue; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsVehicle; -import com.jme3.bullet.objects.VehicleWheel; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyAsset; -import jme3utilities.debug.AxesVisualizer; -import jme3utilities.mesh.PointMesh; - -/** - * Test for Minie issue #13 (vehicle acceleration depends on its location). - * - * @author Stephen Gold sgold@sonic.net - */ -final public class TestIssue13 extends SimpleApplication { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestIssue13.class.getName()); - // ************************************************************************* - // fields - - final static private Random random = new Random(); - // ************************************************************************* - // constructors - - /** - * Instantiate the TestIssue13 application. - */ - public TestIssue13() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestIssue13 application. - * - * @param arguments unused - */ - public static void main(String[] arguments) { - TestIssue13 application = new TestIssue13(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - PhysicsRigidBody.logger2.setLevel(Level.WARNING); - - cam.setLocation(new Vector3f(39f, 64f, 172f)); - cam.setRotation(new Quaternion(-0.013f, 0.98608f, -0.1254f, -0.1084f)); - flyCam.setMoveSpeed(100f); - - Material hiMat = MyAsset.createWireframeMaterial( - assetManager, ColorRGBA.Red, 3f); - Material loMat = MyAsset.createWireframeMaterial( - assetManager, ColorRGBA.Green, 3f); - - // Add axes - float axisLength = 30f; - AxesVisualizer axes = new AxesVisualizer(assetManager, axisLength); - axes.setLineWidth(AxesVisualizer.widthForSolid); - rootNode.addControl(axes); - axes.setEnabled(true); - - for (int i = 0; i < 1000; ++i) { - float x = -50f + 100f * random.nextFloat(); - float z = -50f + 100f * random.nextFloat(); - float vz = test(x, z); - - PointMesh pointMesh = new PointMesh(); - pointMesh.setLocation(new Vector3f(x, 5f * vz, z)); - Geometry geometry = new Geometry("result", pointMesh); - if (vz > 1f) { - geometry.setMaterial(hiMat); - } else { - geometry.setMaterial(loMat); - } - rootNode.attachChild(geometry); - } - } - // ************************************************************************* - // private methods - - private static float test(float startX, float startZ) { - PhysicsSpace physicsSpace - = new PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT); - - // Add a static plane to represent the ground. - Plane plane = new Plane(Vector3f.UNIT_Y, 0f); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody ground - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(ground); - - // Create a wedge-shaped vehicle with a low center of gravity. - // The local forward direction is +Z. - float noseZ = 1.4f; - float spoilerY = 0.5f; - float tailZ = -0.7f; - float undercarriageY = -0.1f; - float halfWidth = 0.4f; - Collection cornerLocations = new ArrayList<>(6); - cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, noseZ)); - cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, noseZ)); - cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, tailZ)); - cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, tailZ)); - cornerLocations.add(new Vector3f(+halfWidth, spoilerY, tailZ)); - cornerLocations.add(new Vector3f(-halfWidth, spoilerY, tailZ)); - HullCollisionShape wedgeShape - = new HullCollisionShape(cornerLocations); - - float mass = 1.525f; - PhysicsVehicle vehicle = new PhysicsVehicle(wedgeShape, mass); - vehicle.setDamping(0.086f, 0f); - vehicle.setSuspensionCompression(6f); - vehicle.setSuspensionDamping(7f); - vehicle.setSuspensionStiffness(150f); - - // Add 4 wheels, 2 in the front (for steering) and 2 in the rear. - boolean front = true; - boolean rear = false; - float frontAxisZ = 0.7f * noseZ; - float rearAxisZ = 0.8f * tailZ; - float radius = 0.425f; // of each tire - float restLength = 0.2f; // of the suspension - float xOffset = 0.9f * halfWidth; - Vector3f axleDirection = new Vector3f(-1f, 0f, 0f); - Vector3f suspensionDirection = new Vector3f(0f, -1f, 0f); - vehicle.addWheel(new Vector3f(-xOffset, 0f, frontAxisZ), - suspensionDirection, axleDirection, restLength, radius, front); - vehicle.addWheel(new Vector3f(xOffset, 0f, frontAxisZ), - suspensionDirection, axleDirection, restLength, radius, front); - vehicle.addWheel(new Vector3f(-xOffset, 0f, rearAxisZ), - suspensionDirection, axleDirection, restLength, radius, rear); - vehicle.addWheel(new Vector3f(xOffset, 0f, rearAxisZ), - suspensionDirection, axleDirection, restLength, radius, rear); - - for (int i = 0; i < 4; ++i) { - VehicleWheel w = vehicle.getWheel(i); - w.setFrictionSlip(0.32f); - w.setMaxSuspensionForce(8f); - w.setRestLength(0.225f); - w.setSuspensionStiffness(10f); - w.setWheelsDampingCompression(2.087f); - w.setWheelsDampingRelaxation(2.845f); - } - - Vector3f startLocation = new Vector3f(startX, 0.382268f, startZ); - vehicle.setPhysicsLocation(startLocation); - physicsSpace.addCollisionObject(vehicle); - - // Simulate a single timestep. - vehicle.accelerate(2, 415.71f); - vehicle.accelerate(3, 415.71f); - physicsSpace.update(1f / 60, 0); - Vector3f velocity = vehicle.getLinearVelocity(); - - physicsSpace.destroy(); - - return velocity.z; - } -} +/* + Copyright (c) 2021-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.issue; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.objects.VehicleWheel; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyAsset; +import jme3utilities.debug.AxesVisualizer; +import jme3utilities.mesh.PointMesh; + +/** + * Test for Minie issue #13 (vehicle acceleration depends on its location). + * + * @author Stephen Gold sgold@sonic.net + */ +final public class TestIssue13 extends SimpleApplication { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue13.class.getName()); + // ************************************************************************* + // fields + + final static private Random random = new Random(); + // ************************************************************************* + // constructors + + /** + * Instantiate the TestIssue13 application. + */ + public TestIssue13() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestIssue13 application. + * + * @param arguments unused + */ + public static void main(String[] arguments) { + TestIssue13 application = new TestIssue13(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + PhysicsRigidBody.logger2.setLevel(Level.WARNING); + + cam.setLocation(new Vector3f(39f, 64f, 172f)); + cam.setRotation(new Quaternion(-0.013f, 0.98608f, -0.1254f, -0.1084f)); + flyCam.setMoveSpeed(100f); + + Material hiMat = MyAsset.createWireframeMaterial( + assetManager, ColorRGBA.Red, 3f); + Material loMat = MyAsset.createWireframeMaterial( + assetManager, ColorRGBA.Green, 3f); + + // Add axes + float axisLength = 30f; + AxesVisualizer axes = new AxesVisualizer(assetManager, axisLength); + axes.setLineWidth(AxesVisualizer.widthForSolid); + rootNode.addControl(axes); + axes.setEnabled(true); + + for (int i = 0; i < 1000; ++i) { + float x = -50f + 100f * random.nextFloat(); + float z = -50f + 100f * random.nextFloat(); + float vz = test(x, z); + + PointMesh pointMesh = new PointMesh(); + pointMesh.setLocation(new Vector3f(x, 5f * vz, z)); + Geometry geometry = new Geometry("result", pointMesh); + if (vz > 1f) { + geometry.setMaterial(hiMat); + } else { + geometry.setMaterial(loMat); + } + rootNode.attachChild(geometry); + } + } + // ************************************************************************* + // private methods + + private static float test(float startX, float startZ) { + PhysicsSpace physicsSpace + = new PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT); + + // Add a static plane to represent the ground. + Plane plane = new Plane(Vector3f.UNIT_Y, 0f); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody ground + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(ground); + + // Create a wedge-shaped vehicle with a low center of gravity. + // The local forward direction is +Z. + float noseZ = 1.4f; + float spoilerY = 0.5f; + float tailZ = -0.7f; + float undercarriageY = -0.1f; + float halfWidth = 0.4f; + Collection cornerLocations = new ArrayList<>(6); + cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, noseZ)); + cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, noseZ)); + cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, tailZ)); + cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, tailZ)); + cornerLocations.add(new Vector3f(+halfWidth, spoilerY, tailZ)); + cornerLocations.add(new Vector3f(-halfWidth, spoilerY, tailZ)); + HullCollisionShape wedgeShape + = new HullCollisionShape(cornerLocations); + + float mass = 1.525f; + PhysicsVehicle vehicle = new PhysicsVehicle(wedgeShape, mass); + vehicle.setDamping(0.086f, 0f); + vehicle.setSuspensionCompression(6f); + vehicle.setSuspensionDamping(7f); + vehicle.setSuspensionStiffness(150f); + + // Add 4 wheels, 2 in the front (for steering) and 2 in the rear. + boolean front = true; + boolean rear = false; + float frontAxisZ = 0.7f * noseZ; + float rearAxisZ = 0.8f * tailZ; + float radius = 0.425f; // of each tire + float restLength = 0.2f; // of the suspension + float xOffset = 0.9f * halfWidth; + Vector3f axleDirection = new Vector3f(-1f, 0f, 0f); + Vector3f suspensionDirection = new Vector3f(0f, -1f, 0f); + vehicle.addWheel(new Vector3f(-xOffset, 0f, frontAxisZ), + suspensionDirection, axleDirection, restLength, radius, front); + vehicle.addWheel(new Vector3f(xOffset, 0f, frontAxisZ), + suspensionDirection, axleDirection, restLength, radius, front); + vehicle.addWheel(new Vector3f(-xOffset, 0f, rearAxisZ), + suspensionDirection, axleDirection, restLength, radius, rear); + vehicle.addWheel(new Vector3f(xOffset, 0f, rearAxisZ), + suspensionDirection, axleDirection, restLength, radius, rear); + + for (int i = 0; i < 4; ++i) { + VehicleWheel w = vehicle.getWheel(i); + w.setFrictionSlip(0.32f); + w.setMaxSuspensionForce(8f); + w.setRestLength(0.225f); + w.setSuspensionStiffness(10f); + w.setWheelsDampingCompression(2.087f); + w.setWheelsDampingRelaxation(2.845f); + } + + Vector3f startLocation = new Vector3f(startX, 0.382268f, startZ); + vehicle.setPhysicsLocation(startLocation); + physicsSpace.addCollisionObject(vehicle); + + // Simulate a single timestep. + vehicle.accelerate(2, 415.71f); + vehicle.accelerate(3, 415.71f); + physicsSpace.update(1f / 60, 0); + Vector3f velocity = vehicle.getLinearVelocity(); + + physicsSpace.destroy(); + + return velocity.z; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Gimpact.java b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Gimpact.java index 43678c9a2..c23631034 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Gimpact.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Gimpact.java @@ -1,228 +1,228 @@ -/* - Copyright (c) 2021-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.issue; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.GImpactCollisionShape; -import com.jme3.bullet.control.BetterCharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import java.util.logging.Logger; - -/** - * Test for Minie issue #18 (BetterCharacterController hops across seams) using - * a GImpactCollisionShape. If the issue is present, numeric data will be - * printed to {@code System.out}. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class TestIssue18Gimpact - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestIssue18Gimpact.class.getName()); - // ************************************************************************* - // fields - - /** - * control under test - */ - private static BetterCharacterControl bcc; - /** - * true if character will move toward +X, false if it will move toward -X - */ - private static boolean increasingX; - /** - * largest Y value seen so far: anything larger than 0.05 is an issue - */ - private static float maxElevation = 0.05f; - /** - * count of physics timesteps simulated - */ - private static int tickCount = 0; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestIssue18Gimpact application. - */ - public TestIssue18Gimpact() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestIssue18Gimpact application. - * - * @param arguments unused - */ - public static void main(String[] arguments) { - TestIssue18Gimpact application = new TestIssue18Gimpact(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - PhysicsSpace physicsSpace = configurePhysics(); - addGround(physicsSpace); - - Node controlledNode = new Node("controlled node"); - rootNode.attachChild(controlledNode); - - float characterRadius = 1f; - float characterHeight = 4f; - float characterMass = 1f; - bcc = new BetterCharacterControl( - characterRadius, characterHeight, characterMass); - controlledNode.addControl(bcc); - physicsSpace.add(bcc); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Terminate the test after 200 time steps. - if (tickCount > 200) { - stop(); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just after the physics has been stepped. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // Determine the character's elevation and print it if it's a new high. - PhysicsRigidBody body = bcc.getRigidBody(); - Vector3f location = body.getPhysicsLocation(); - if (location.y > maxElevation) { - maxElevation = location.y; - System.out.println(tickCount + ": " + location); - } - } - - /** - * Callback from Bullet, invoked just before the physics is stepped. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - ++tickCount; - - // Walk rapidly back and forth across the seam between the 2 triangles. - Vector3f desiredVelocity = new Vector3f(); - float walkSpeed = 99f; - if (increasingX) { - desiredVelocity.x = walkSpeed; - } else { - desiredVelocity.x = -walkSpeed; - } - - Vector3f location = bcc.getRigidBody().getPhysicsLocation(); - if (increasingX && location.x > 7f) { - // stop and reverse direction - desiredVelocity.zero(); - increasingX = false; - } else if (!increasingX && location.x < -7f) { - // stop and reverse direction - desiredVelocity.zero(); - increasingX = true; - } - - bcc.setWalkDirection(desiredVelocity); - } - // ************************************************************************* - // private methods - - /** - * Add a ground body to the specified PhysicsSpace. - * - * @param physicsSpace (not null) - */ - private static void addGround(PhysicsSpace physicsSpace) { - Mesh quad = new Quad(1000f, 1000f); - Spatial ground = new Geometry("ground", quad); - ground.move(-500f, 0f, 500f); - ground.rotate(-FastMath.HALF_PI, 0f, 0f); - - CollisionShape shape = new GImpactCollisionShape(quad); - // shape.setContactFilterEnabled(false); // to make the test fail - RigidBodyControl rbc - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - rbc.setPhysicsSpace(physicsSpace); - ground.addControl(rbc); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - result.addTickListener(this); - - return result; - } -} +/* + Copyright (c) 2021-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.issue; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.GImpactCollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import java.util.logging.Logger; + +/** + * Test for Minie issue #18 (BetterCharacterController hops across seams) using + * a GImpactCollisionShape. If the issue is present, numeric data will be + * printed to {@code System.out}. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class TestIssue18Gimpact + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue18Gimpact.class.getName()); + // ************************************************************************* + // fields + + /** + * control under test + */ + private static BetterCharacterControl bcc; + /** + * true if character will move toward +X, false if it will move toward -X + */ + private static boolean increasingX; + /** + * largest Y value seen so far: anything larger than 0.05 is an issue + */ + private static float maxElevation = 0.05f; + /** + * count of physics timesteps simulated + */ + private static int tickCount = 0; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestIssue18Gimpact application. + */ + public TestIssue18Gimpact() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestIssue18Gimpact application. + * + * @param arguments unused + */ + public static void main(String[] arguments) { + TestIssue18Gimpact application = new TestIssue18Gimpact(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + PhysicsSpace physicsSpace = configurePhysics(); + addGround(physicsSpace); + + Node controlledNode = new Node("controlled node"); + rootNode.attachChild(controlledNode); + + float characterRadius = 1f; + float characterHeight = 4f; + float characterMass = 1f; + bcc = new BetterCharacterControl( + characterRadius, characterHeight, characterMass); + controlledNode.addControl(bcc); + physicsSpace.add(bcc); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Terminate the test after 200 time steps. + if (tickCount > 200) { + stop(); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just after the physics has been stepped. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // Determine the character's elevation and print it if it's a new high. + PhysicsRigidBody body = bcc.getRigidBody(); + Vector3f location = body.getPhysicsLocation(); + if (location.y > maxElevation) { + maxElevation = location.y; + System.out.println(tickCount + ": " + location); + } + } + + /** + * Callback from Bullet, invoked just before the physics is stepped. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + ++tickCount; + + // Walk rapidly back and forth across the seam between the 2 triangles. + Vector3f desiredVelocity = new Vector3f(); + float walkSpeed = 99f; + if (increasingX) { + desiredVelocity.x = walkSpeed; + } else { + desiredVelocity.x = -walkSpeed; + } + + Vector3f location = bcc.getRigidBody().getPhysicsLocation(); + if (increasingX && location.x > 7f) { + // stop and reverse direction + desiredVelocity.zero(); + increasingX = false; + } else if (!increasingX && location.x < -7f) { + // stop and reverse direction + desiredVelocity.zero(); + increasingX = true; + } + + bcc.setWalkDirection(desiredVelocity); + } + // ************************************************************************* + // private methods + + /** + * Add a ground body to the specified PhysicsSpace. + * + * @param physicsSpace (not null) + */ + private static void addGround(PhysicsSpace physicsSpace) { + Mesh quad = new Quad(1000f, 1000f); + Spatial ground = new Geometry("ground", quad); + ground.move(-500f, 0f, 500f); + ground.rotate(-FastMath.HALF_PI, 0f, 0f); + + CollisionShape shape = new GImpactCollisionShape(quad); + // shape.setContactFilterEnabled(false); // to make the test fail + RigidBodyControl rbc + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + rbc.setPhysicsSpace(physicsSpace); + ground.addControl(rbc); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + result.addTickListener(this); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Heightfield.java b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Heightfield.java index 1a38a6fbd..17faf08f0 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Heightfield.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Heightfield.java @@ -1,221 +1,221 @@ -/* - Copyright (c) 2021-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.issue; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.control.BetterCharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import java.util.logging.Logger; - -/** - * Test for Minie issue #18 (BetterCharacterController hops across seams) using - * a HeightfieldCollisionShape. If the issue is present, numeric data will be - * printed to {@code System.out}. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class TestIssue18Heightfield - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestIssue18Heightfield.class.getName()); - // ************************************************************************* - // fields - - /** - * control under test - */ - private static BetterCharacterControl bcc; - /** - * true if character will move toward +X, false if it will move toward -X - */ - private static boolean increasingX; - /** - * largest Y value seen so far: anything larger than 0.05 is an issue - */ - private static float maxElevation = 0.05f; - /** - * count of physics timesteps simulated - */ - private static int tickCount = 0; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestIssue18Heightfield application. - */ - public TestIssue18Heightfield() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestIssue18Heightfield application. - * - * @param arguments unused - */ - public static void main(String[] arguments) { - TestIssue18Heightfield application = new TestIssue18Heightfield(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - PhysicsSpace physicsSpace = configurePhysics(); - addGround(physicsSpace); - - Node controlledNode = new Node("controlled node"); - rootNode.attachChild(controlledNode); - - float characterRadius = 1f; - float characterHeight = 4f; - float characterMass = 1f; - bcc = new BetterCharacterControl( - characterRadius, characterHeight, characterMass); - controlledNode.addControl(bcc); - physicsSpace.add(bcc); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Terminate the test after 200 time steps. - if (tickCount > 200) { - stop(); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just after the physics has been stepped. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // Determine the character's elevation and print it if it's a new high. - PhysicsRigidBody body = bcc.getRigidBody(); - Vector3f location = body.getPhysicsLocation(); - if (location.y > maxElevation) { - maxElevation = location.y; - System.out.println(tickCount + ": " + location); - } - } - - /** - * Callback from Bullet, invoked just before the physics is stepped. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - ++tickCount; - - // Walk rapidly back and forth across the seam between the 2 triangles. - Vector3f desiredVelocity = new Vector3f(); - float walkSpeed = 99f; - if (increasingX) { - desiredVelocity.x = walkSpeed; - } else { - desiredVelocity.x = -walkSpeed; - } - - Vector3f location = bcc.getRigidBody().getPhysicsLocation(); - if (increasingX && location.x > 7f) { - // stop and reverse direction - desiredVelocity.zero(); - increasingX = false; - } else if (!increasingX && location.x < -7f) { - // stop and reverse direction - desiredVelocity.zero(); - increasingX = true; - } - - bcc.setWalkDirection(desiredVelocity); - } - // ************************************************************************* - // private methods - - /** - * Add a ground body to the specified PhysicsSpace. - * - * @param physicsSpace (not null) - */ - private static void addGround(PhysicsSpace physicsSpace) { - float[] heightmap = {0f, 0f, 0f, 0f}; - Vector3f scale = new Vector3f(1000f, 1f, 1000f); - CollisionShape shape = new HeightfieldCollisionShape(heightmap, scale); - // shape.setContactFilterEnabled(false); // to make the test fail - - RigidBodyControl rbc - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - rbc.setPhysicsSpace(physicsSpace); - new Node().addControl(rbc); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - result.addTickListener(this); - - return result; - } -} +/* + Copyright (c) 2021-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.issue; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import java.util.logging.Logger; + +/** + * Test for Minie issue #18 (BetterCharacterController hops across seams) using + * a HeightfieldCollisionShape. If the issue is present, numeric data will be + * printed to {@code System.out}. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class TestIssue18Heightfield + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue18Heightfield.class.getName()); + // ************************************************************************* + // fields + + /** + * control under test + */ + private static BetterCharacterControl bcc; + /** + * true if character will move toward +X, false if it will move toward -X + */ + private static boolean increasingX; + /** + * largest Y value seen so far: anything larger than 0.05 is an issue + */ + private static float maxElevation = 0.05f; + /** + * count of physics timesteps simulated + */ + private static int tickCount = 0; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestIssue18Heightfield application. + */ + public TestIssue18Heightfield() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestIssue18Heightfield application. + * + * @param arguments unused + */ + public static void main(String[] arguments) { + TestIssue18Heightfield application = new TestIssue18Heightfield(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + PhysicsSpace physicsSpace = configurePhysics(); + addGround(physicsSpace); + + Node controlledNode = new Node("controlled node"); + rootNode.attachChild(controlledNode); + + float characterRadius = 1f; + float characterHeight = 4f; + float characterMass = 1f; + bcc = new BetterCharacterControl( + characterRadius, characterHeight, characterMass); + controlledNode.addControl(bcc); + physicsSpace.add(bcc); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Terminate the test after 200 time steps. + if (tickCount > 200) { + stop(); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just after the physics has been stepped. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // Determine the character's elevation and print it if it's a new high. + PhysicsRigidBody body = bcc.getRigidBody(); + Vector3f location = body.getPhysicsLocation(); + if (location.y > maxElevation) { + maxElevation = location.y; + System.out.println(tickCount + ": " + location); + } + } + + /** + * Callback from Bullet, invoked just before the physics is stepped. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + ++tickCount; + + // Walk rapidly back and forth across the seam between the 2 triangles. + Vector3f desiredVelocity = new Vector3f(); + float walkSpeed = 99f; + if (increasingX) { + desiredVelocity.x = walkSpeed; + } else { + desiredVelocity.x = -walkSpeed; + } + + Vector3f location = bcc.getRigidBody().getPhysicsLocation(); + if (increasingX && location.x > 7f) { + // stop and reverse direction + desiredVelocity.zero(); + increasingX = false; + } else if (!increasingX && location.x < -7f) { + // stop and reverse direction + desiredVelocity.zero(); + increasingX = true; + } + + bcc.setWalkDirection(desiredVelocity); + } + // ************************************************************************* + // private methods + + /** + * Add a ground body to the specified PhysicsSpace. + * + * @param physicsSpace (not null) + */ + private static void addGround(PhysicsSpace physicsSpace) { + float[] heightmap = {0f, 0f, 0f, 0f}; + Vector3f scale = new Vector3f(1000f, 1f, 1000f); + CollisionShape shape = new HeightfieldCollisionShape(heightmap, scale); + // shape.setContactFilterEnabled(false); // to make the test fail + + RigidBodyControl rbc + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + rbc.setPhysicsSpace(physicsSpace); + new Node().addControl(rbc); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + result.addTickListener(this); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Mesh.java b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Mesh.java index 8178ea512..a01f4da65 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Mesh.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/TestIssue18Mesh.java @@ -1,228 +1,228 @@ -/* - Copyright (c) 2021-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.issue; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.control.BetterCharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import java.util.logging.Logger; - -/** - * Test for Minie issue #18 (BetterCharacterController hops across seams) using - * a MeshCollisionShape. If the issue is present, numeric data will be printed - * to {@code System.out}. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class TestIssue18Mesh - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(TestIssue18Mesh.class.getName()); - // ************************************************************************* - // fields - - /** - * control under test - */ - private static BetterCharacterControl bcc; - /** - * true if character will move toward +X, false if it will move toward -X - */ - private static boolean increasingX; - /** - * largest Y value seen so far: anything larger than 0.05 is an issue - */ - private static float maxElevation = 0.05f; - /** - * count of physics timesteps simulated - */ - private static int tickCount = 0; - // ************************************************************************* - // constructors - - /** - * Instantiate the TestIssue18Mesh application. - */ - public TestIssue18Mesh() { // explicit to avoid warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the TestIssue18Mesh application. - * - * @param arguments unused - */ - public static void main(String[] arguments) { - TestIssue18Mesh application = new TestIssue18Mesh(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - PhysicsSpace physicsSpace = configurePhysics(); - addGround(physicsSpace); - - Node controlledNode = new Node("controlled node"); - rootNode.attachChild(controlledNode); - - float characterRadius = 1f; - float characterHeight = 4f; - float characterMass = 1f; - bcc = new BetterCharacterControl( - characterRadius, characterHeight, characterMass); - controlledNode.addControl(bcc); - physicsSpace.add(bcc); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Terminate the test after 200 time steps. - if (tickCount > 200) { - stop(); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just after the physics has been stepped. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // Determine the character's elevation and print it if it's a new high. - PhysicsRigidBody body = bcc.getRigidBody(); - Vector3f location = body.getPhysicsLocation(); - if (location.y > maxElevation) { - maxElevation = location.y; - System.out.println(tickCount + ": " + location); - } - } - - /** - * Callback from Bullet, invoked just before the physics is stepped. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - ++tickCount; - - // Walk rapidly back and forth across the seam between the 2 triangles. - Vector3f desiredVelocity = new Vector3f(); - float walkSpeed = 99f; - if (increasingX) { - desiredVelocity.x = walkSpeed; - } else { - desiredVelocity.x = -walkSpeed; - } - - Vector3f location = bcc.getRigidBody().getPhysicsLocation(); - if (increasingX && location.x > 7f) { - // stop and reverse direction - desiredVelocity.zero(); - increasingX = false; - } else if (!increasingX && location.x < -7f) { - // stop and reverse direction - desiredVelocity.zero(); - increasingX = true; - } - - bcc.setWalkDirection(desiredVelocity); - } - // ************************************************************************* - // private methods - - /** - * Add a ground body to the specified PhysicsSpace. - * - * @param physicsSpace (not null) - */ - private static void addGround(PhysicsSpace physicsSpace) { - Mesh quad = new Quad(1000f, 1000f); - Spatial ground = new Geometry("ground", quad); - ground.move(-500f, 0f, 500f); - ground.rotate(-FastMath.HALF_PI, 0f, 0f); - - CollisionShape shape = CollisionShapeFactory.createMeshShape(ground); - // shape.setContactFilterEnabled(false); // to make the test fail - RigidBodyControl rbc - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - rbc.setPhysicsSpace(physicsSpace); - ground.addControl(rbc); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - result.addTickListener(this); - - return result; - } -} +/* + Copyright (c) 2021-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.issue; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import java.util.logging.Logger; + +/** + * Test for Minie issue #18 (BetterCharacterController hops across seams) using + * a MeshCollisionShape. If the issue is present, numeric data will be printed + * to {@code System.out}. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class TestIssue18Mesh + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue18Mesh.class.getName()); + // ************************************************************************* + // fields + + /** + * control under test + */ + private static BetterCharacterControl bcc; + /** + * true if character will move toward +X, false if it will move toward -X + */ + private static boolean increasingX; + /** + * largest Y value seen so far: anything larger than 0.05 is an issue + */ + private static float maxElevation = 0.05f; + /** + * count of physics timesteps simulated + */ + private static int tickCount = 0; + // ************************************************************************* + // constructors + + /** + * Instantiate the TestIssue18Mesh application. + */ + public TestIssue18Mesh() { // explicit to avoid warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestIssue18Mesh application. + * + * @param arguments unused + */ + public static void main(String[] arguments) { + TestIssue18Mesh application = new TestIssue18Mesh(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + PhysicsSpace physicsSpace = configurePhysics(); + addGround(physicsSpace); + + Node controlledNode = new Node("controlled node"); + rootNode.attachChild(controlledNode); + + float characterRadius = 1f; + float characterHeight = 4f; + float characterMass = 1f; + bcc = new BetterCharacterControl( + characterRadius, characterHeight, characterMass); + controlledNode.addControl(bcc); + physicsSpace.add(bcc); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Terminate the test after 200 time steps. + if (tickCount > 200) { + stop(); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just after the physics has been stepped. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // Determine the character's elevation and print it if it's a new high. + PhysicsRigidBody body = bcc.getRigidBody(); + Vector3f location = body.getPhysicsLocation(); + if (location.y > maxElevation) { + maxElevation = location.y; + System.out.println(tickCount + ": " + location); + } + } + + /** + * Callback from Bullet, invoked just before the physics is stepped. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + ++tickCount; + + // Walk rapidly back and forth across the seam between the 2 triangles. + Vector3f desiredVelocity = new Vector3f(); + float walkSpeed = 99f; + if (increasingX) { + desiredVelocity.x = walkSpeed; + } else { + desiredVelocity.x = -walkSpeed; + } + + Vector3f location = bcc.getRigidBody().getPhysicsLocation(); + if (increasingX && location.x > 7f) { + // stop and reverse direction + desiredVelocity.zero(); + increasingX = false; + } else if (!increasingX && location.x < -7f) { + // stop and reverse direction + desiredVelocity.zero(); + increasingX = true; + } + + bcc.setWalkDirection(desiredVelocity); + } + // ************************************************************************* + // private methods + + /** + * Add a ground body to the specified PhysicsSpace. + * + * @param physicsSpace (not null) + */ + private static void addGround(PhysicsSpace physicsSpace) { + Mesh quad = new Quad(1000f, 1000f); + Spatial ground = new Geometry("ground", quad); + ground.move(-500f, 0f, 500f); + ground.rotate(-FastMath.HALF_PI, 0f, 0f); + + CollisionShape shape = CollisionShapeFactory.createMeshShape(ground); + // shape.setContactFilterEnabled(false); // to make the test fail + RigidBodyControl rbc + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + rbc.setPhysicsSpace(physicsSpace); + ground.addControl(rbc); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + result.addTickListener(this); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/package-info.java index 47c032111..b1e2604a7 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/issue/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/issue/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Applications to test whether certain issues have regressed. - */ -package jme3utilities.minie.test.issue; +/* + Copyright (c) 2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Applications to test whether certain issues have regressed. + */ +package jme3utilities.minie.test.issue; diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/TubeTreeMesh.java b/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/TubeTreeMesh.java index 2b9124b13..2c3cf11e8 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/TubeTreeMesh.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/TubeTreeMesh.java @@ -1,715 +1,715 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.mesh; - -import com.jme3.anim.Armature; -import com.jme3.anim.Joint; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.BufferUtils; -import com.jme3.util.clone.Cloner; -import java.io.IOException; -import java.nio.FloatBuffer; -import java.util.BitSet; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyMesh; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyQuaternion; -import jme3utilities.math.MyVector3f; - -/** - * An animated Triangles-mode Mesh (with indices and normals, but no texture - * coordinates) for a branching 3-D shape that conforms to an Armature. Can be - * used to visualize ropes, hoses, snakes, and such. TODO add texture - * coordinates - * - * @author Stephen Gold sgold@sonic.net - */ -public class TubeTreeMesh extends Mesh { - // ************************************************************************* - // constants and loggers - - /** - * maximum number of weights per vertex - */ - final private static int maxWpv = 4; - /** - * number of axes in a vector - */ - final private static int numAxes = 3; - /** - * number of vertices per triangle - */ - final private static int vpt = 3; - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(TubeTreeMesh.class.getName()); - /** - * local copy of {@link com.jme3.math.Vector3f#ZERO} - */ - final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f); - // ************************************************************************* - // fields - - /** - * Armature used to construct the Mesh - */ - private Armature armature; - /** - * overshoot distance for leaf joints (in mesh units, default=0) - */ - private float leafOvershoot; - /** - * radius of each mesh loop (in mesh units) - */ - private float radius; - /** - * normal buffer - */ - private FloatBuffer normalBuffer; - /** - * weight buffer - */ - private FloatBuffer weightBuffer; - /** - * position buffer - */ - private FloatBuffer positionBuffer; - /** - * index buffer - */ - private IndexBuffer indexBuffer; - /** - * total number of vertices in the mesh - */ - private int numVertices; - /** - * number of mesh loops in each tube segment (default=3) - */ - private int loopsPerSegment; - /** - * number of sample points per mesh loop (default=12) - */ - private int samplesPerLoop; - /** - * cached sample positions for a unit circle in the X-Y plane - */ - private Vector3f[] circleSamples = null; - /** - * reusable samples to transform - */ - private Vector3f[] reusable = { - new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f(), - new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f() - }; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected TubeTreeMesh() { - } - - /** - * Instantiate a tube-tree mesh based on the specified Armature and radius. - * - * @param armature (not null, in bind pose, unaffected) - * @param radius the desired radius of each mesh loop (in mesh units, >0) - */ - public TubeTreeMesh(Armature armature, float radius) { - this(armature, radius, 0f, 3, 12); - } - - /** - * Instantiate a tube-tree mesh based on the specified Armature and - * parameters. - * - * @param armature (not null, in bind pose, unaffected) - * @param radius the desired radius of each mesh loop (in mesh units, >0) - * @param leafOvershoot the desired overshoot distance for leaf joints (in - * mesh units) - * @param loopsPerSegment the desired number of mesh loops in each tube - * segment (≥1) - * @param samplesPerLoop the desired number of samples in each mesh loop - * (≥3) - */ - public TubeTreeMesh(Armature armature, float radius, float leafOvershoot, - int loopsPerSegment, int samplesPerLoop) { - Validate.nonNull(armature, "armature"); - Validate.positive(radius, "radius"); - Validate.positive(loopsPerSegment, "loops per segment"); - Validate.inRange( - samplesPerLoop, "samples per loop", 3, Integer.MAX_VALUE); - - this.armature = Heart.deepCopy(armature); - this.armature.update(); - - this.radius = radius; - this.leafOvershoot = leafOvershoot; - this.loopsPerSegment = loopsPerSegment; - this.samplesPerLoop = samplesPerLoop; - - setMaxNumWeights(2); - updateAll(); - } - // ************************************************************************* - // new methods exposed - - /** - * Enumerate the indices of all cap vertices for the named Joint. - * - * @param jointName the name of the Joint - * @return a new BitSet of vertex indices (not null) - */ - public BitSet listCapVertices(String jointName) { - Joint joint = armature.getJoint(jointName); - if (joint == null) { - String message = "no such joint: " + MyString.quote(jointName); - throw new IllegalArgumentException(message); - } - - int numJoints = armature.getJointCount(); - int trianglesPerCap = samplesPerLoop - 2; - int trianglesPerLoop = 2 * samplesPerLoop; - int trianglesPerSegment = trianglesPerLoop * loopsPerSegment; - - BitSet result = new BitSet(numVertices); - int triCount = 0; - - for (int childIndex = 0; childIndex < numJoints; ++childIndex) { - Joint child = armature.getJoint(childIndex); - Joint parent = child.getParent(); - if (parent != null) { - triCount += trianglesPerSegment; - - if (capChild(child)) { - if (child == joint) { - int fromIndex = vpt * triCount; - int toIndex = vpt * (triCount + trianglesPerCap); - result.set(fromIndex, toIndex); - } - triCount += trianglesPerCap; - } - - if (capParent(parent)) { - if (parent == joint) { - int fromIndex = vpt * triCount; - int toIndex = vpt * (triCount + trianglesPerCap); - result.set(fromIndex, toIndex); - } - triCount += trianglesPerCap; - } - } - } - - return result; - } - - /** - * Calculate the number of vertices in each cap. - * - * @return count (≥3) - */ - public int verticesPerCap() { - int trianglesPerCap = samplesPerLoop - 2; - int result = vpt * trianglesPerCap; - - return result; - } - // ************************************************************************* - // Mesh methods - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned mesh into a deep-cloned one, using the specified Cloner - * and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this mesh (not null) - * @param original the mesh from which this mesh was shallow-cloned (unused) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - super.cloneFields(cloner, original); - - this.weightBuffer = cloner.clone(weightBuffer); - this.normalBuffer = cloner.clone(normalBuffer); - this.positionBuffer = cloner.clone(positionBuffer); - this.indexBuffer = cloner.clone(indexBuffer); - // armature not cloned (read-only) - assert circleSamples == null : circleSamples; - this.reusable = cloner.clone(reusable); - } - - /** - * De-serialize this mesh from the specified importer, for example when - * loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - super.read(importer); - InputCapsule capsule = importer.getCapsule(this); - - this.leafOvershoot = capsule.readFloat("leafOvershoot", 0f); - this.radius = capsule.readFloat("radius", 1f); - this.loopsPerSegment = capsule.readInt("loopsPerSegment", 3); - this.samplesPerLoop = capsule.readInt("samplesPerLoop", 12); - this.armature = (Armature) capsule.readSavable("armature", null); - - // Recalculate the derived properties. - updateDerivedProperties(); - } - - /** - * Serialize this Mesh to the specified exporter, for example when saving to - * a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - super.write(exporter); - OutputCapsule capsule = exporter.getCapsule(this); - - capsule.write(leafOvershoot, "leafOvershoot", 0f); - capsule.write(radius, "radius", 1f); - capsule.write(loopsPerSegment, "loopsPerSegment", 3); - capsule.write(samplesPerLoop, "samplesPerLoop", 12); - capsule.write(armature, "armature", null); - } - // ************************************************************************* - // private methods - - /** - * Allocate new buffers for mesh data. - */ - private void allocateBuffers() { - int weightCount = maxWpv * numVertices; - this.indexBuffer - = IndexBuffer.createIndexBuffer(numVertices, weightCount); - MyMesh.setBoneIndexBuffer(this, maxWpv, indexBuffer); - this.weightBuffer - = BufferUtils.createFloatBuffer(weightCount); - setBuffer(VertexBuffer.Type.BoneWeight, maxWpv, weightBuffer); - - this.positionBuffer = BufferUtils.createVector3Buffer(numVertices); - setBuffer(VertexBuffer.Type.BindPosePosition, numAxes, positionBuffer); - this.normalBuffer = BufferUtils.createVector3Buffer(numVertices); - setBuffer(VertexBuffer.Type.BindPoseNormal, numAxes, normalBuffer); - - FloatBuffer pb = BufferUtils.createVector3Buffer(numVertices); - setBuffer(VertexBuffer.Type.Position, numAxes, pb); - FloatBuffer nb = BufferUtils.createVector3Buffer(numVertices); - setBuffer(VertexBuffer.Type.Normal, numAxes, nb); - } - - /** - * Test whether segments having the specified Joint as the child should be - * capped at the child's end. - * - * @param child the Joint to test (not null, unaffected) - * @return true if capped, otherwise false - */ - private static boolean capChild(Joint child) { - int numChildren = child.getChildren().size(); - if (numChildren == 1) { - return false; - } else { - return true; // child is a leaf or fork: cap it - } - } - - /** - * Test whether segments having the specified Joint as the parent should be - * capped at the parent's end. - * - * @param parent the Joint to test (not null, unaffected) - * @return true if capped, otherwise false - */ - private static boolean capParent(Joint parent) { - Joint grandparent = parent.getParent(); - boolean isCapped; - if (grandparent == null) { - isCapped = true; // parent is a root: cap it - } else { - int numChildren = parent.getChildren().size(); - if (numChildren > 1) { - isCapped = true; // parent is a fork: cap it - } else { - isCapped = false; - } - } - return isCapped; - } - - /** - * Pre-compute sample coordinates for a unit circle in the X-Y plane, - * centered at the origin. - */ - private void initCircleSamples() { - this.circleSamples = new Vector3f[samplesPerLoop + 1]; - for (int sampleI = 0; sampleI <= samplesPerLoop; ++sampleI) { - float theta = (FastMath.TWO_PI / samplesPerLoop) * sampleI; - float cos = FastMath.cos(theta); - float sin = FastMath.sin(theta); - this.circleSamples[sampleI] = new Vector3f(cos, sin, 0f); - } - } - - /** - * Test whether the specified Joint is a leaf. - * - * @param joint the Joint to test (not null, unaffected) - * @return true for a leaf, otherwise false - */ - private static boolean isLeaf(Joint joint) { - boolean result = joint.getChildren().isEmpty(); - return result; - } - - /** - * Write joint indices and weights for a triangle animated entirely by a - * single Joint. - * - * @param jointIndex the index of the Joint that animates the triangle - */ - private void putAnimationForTriangle(int jointIndex) { - for (int vertexIndex = 0; vertexIndex < vpt; ++vertexIndex) { - indexBuffer.put(jointIndex); - weightBuffer.put(1f); - - for (int weightIndex = 1; weightIndex < maxWpv; ++weightIndex) { - indexBuffer.put(0); - weightBuffer.put(0f); - } - } - } - - /** - * Write joint indices and weights for a vertex animated by 2 joints. - * - * @param jointIndex1 the index of the first Joint (≥0) - * @param jointIndex2 the index of the 2nd Joint (≥0) - * @param w1 the weight for the first Joint - */ - private void putAnimationForVertex( - int jointIndex1, int jointIndex2, float w1) { - float weight1 = FastMath.clamp(w1, 0f, 1f); - - int weightIndex; - if (weight1 != 0f) { - indexBuffer.put(jointIndex1); - weightBuffer.put(weight1); - weightIndex = 2; - } else { - weightIndex = 1; - } - - indexBuffer.put(jointIndex2); - weightBuffer.put(1f - weight1); - - while (weightIndex < maxWpv) { - indexBuffer.put(0); - weightBuffer.put(0f); - ++weightIndex; - } - } - - /** - * Write a flat, circular cap to the buffers. TODO more cap-shape options - * - * @param centerPos the position of the center of the cap (in mesh - * coordinates, not null, unaffected) - * @param orientation the orientation of the cap (cap lies in the X-Y plane) - * (in mesh coordinates, not null, unaffected) - * @param posZ the Z component of the cap position (in local coordinates) - * @param normalZ the Z component of the normal direction (in local - * coordinates, +1 or -1) - * @param jointIndex (≥0) - */ - private void putCap(Vector3f centerPos, Quaternion orientation, float posZ, - float normalZ, int jointIndex) { - Vector3f a = reusable[0]; - Vector3f b = reusable[1]; - Vector3f c = reusable[2]; - - int startBufferOffset = positionBuffer.position(); - - // Put a triangle for each sample point except the first and last. - for (int triIndex = 1; triIndex < samplesPerLoop - 1; ++triIndex) { - Vector3f pos1 = circleSamples[0]; - Vector3f pos2; - Vector3f pos3; - if (normalZ > 0f) { - pos2 = circleSamples[triIndex]; - pos3 = circleSamples[triIndex + 1]; - } else { - pos2 = circleSamples[triIndex + 1]; - pos3 = circleSamples[triIndex]; - } - - a.set(radius * pos1.x, radius * pos1.y, posZ); - b.set(radius * pos2.x, radius * pos2.y, posZ); - c.set(radius * pos3.x, radius * pos3.y, posZ); - putTransformedTriangle( - positionBuffer, centerPos, orientation, a, b, c); - - a.set(0f, 0f, normalZ); - b.set(0f, 0f, normalZ); - c.set(0f, 0f, normalZ); - putTransformedTriangle( - normalBuffer, translateIdentity, orientation, a, b, c); - - putAnimationForTriangle(jointIndex); - } - int numFloats = positionBuffer.position() - startBufferOffset; - assert numFloats == numAxes * verticesPerCap(); - } - - /** - * Rotate and translate each vertex of a triangle and write the transformed - * coordinates to the specified buffer. - * - * @param buffer the buffer to write to (not null, modified) - * @param offset the translation to apply (not null, unaffected) - * @param rotation the rotation to apply (not null, unaffected) - * @param v1 the first vertex vector to transform (not null, modified) - * @param v2 the 2nd vertex vector to transform (not null, modified) - * @param v3 the 3rd vertex vector to transform (not null, modified) - */ - private static void putTransformedTriangle( - FloatBuffer buffer, Vector3f offset, Quaternion rotation, - Vector3f v1, Vector3f v2, Vector3f v3) { - MyQuaternion.rotate(rotation, v1, v1); - v1.addLocal(offset); - buffer.put(v1.x).put(v1.y).put(v1.z); - - MyQuaternion.rotate(rotation, v2, v2); - v2.addLocal(offset); - buffer.put(v2.x).put(v2.y).put(v2.z); - - MyQuaternion.rotate(rotation, v3, v3); - v3.addLocal(offset); - buffer.put(v3.x).put(v3.y).put(v3.z); - } - - /** - * Write an uncapped, cylindrical tube segment to the buffers. - * - * @param origin the origin of the segment's local coordinate system (in - * mesh coordinates, not null, unaffected) - * @param orientation the orientation of the segment's local coordinate - * system (in mesh coordinates, the local +Z axis being the length axis, not - * null, unaffected) - * @param startZ the Z component of the start position (in local - * coordinates) - * @param endZ the Z component of the end position (in local coordinates) - * @param startJointIndex (≥0) - * @param endJointIndex (≥0) - */ - private void putTube(Vector3f origin, Quaternion orientation, float startZ, - float endZ, int startJointIndex, int endJointIndex) { - assert startJointIndex != endJointIndex; - - Vector3f pos11 = reusable[0]; - Vector3f pos12 = reusable[1]; - Vector3f pos21 = reusable[2]; - Vector3f pos22 = reusable[3]; - Vector3f norm11 = reusable[4]; - Vector3f norm12 = reusable[5]; - Vector3f norm21 = reusable[6]; - Vector3f norm22 = reusable[7]; - - float fractionStep = 1f / loopsPerSegment; - for (int loopIndex = 0; loopIndex < loopsPerSegment; ++loopIndex) { - float fraction1 = fractionStep * loopIndex; - float fraction2 = fractionStep * (loopIndex + 1); - float z1 = MyMath.lerp(fraction1, startZ, endZ); - float z2 = MyMath.lerp(fraction2, startZ, endZ); - - // a rectangular patch for each sample point - for (int rectIndex = 0; rectIndex < samplesPerLoop; ++rectIndex) { - float x1 = circleSamples[rectIndex].x * radius; - float y1 = circleSamples[rectIndex].y * radius; - float x2 = circleSamples[rectIndex + 1].x * radius; - float y2 = circleSamples[rectIndex + 1].y * radius; - Vector3f normal1 = circleSamples[rectIndex]; - Vector3f normal2 = circleSamples[rectIndex + 1]; - - // Put the lower-right triangle. - pos11.set(x1, y1, z1); - pos21.set(x2, y2, z1); - pos22.set(x2, y2, z2); - putTransformedTriangle(positionBuffer, origin, - orientation, pos11, pos21, pos22); - - norm11.set(normal1.x, normal1.y, 0f); - norm21.set(normal2.x, normal2.y, 0f); - norm22.set(normal2.x, normal2.y, 0f); - putTransformedTriangle(normalBuffer, translateIdentity, - orientation, norm11, norm21, norm22); - - putAnimationForVertex( - endJointIndex, startJointIndex, fraction1); - putAnimationForVertex( - endJointIndex, startJointIndex, fraction1); - putAnimationForVertex( - endJointIndex, startJointIndex, fraction2); - - // Put the upper-left triangle. - pos11.set(x1, y1, z1); - pos12.set(x1, y1, z2); - pos22.set(x2, y2, z2); - putTransformedTriangle(positionBuffer, origin, - orientation, pos11, pos22, pos12); - - norm11.set(normal1.x, normal1.y, 0f); - norm12.set(normal1.x, normal1.y, 0f); - norm22.set(normal2.x, normal2.y, 0f); - putTransformedTriangle(normalBuffer, translateIdentity, - orientation, norm11, norm22, norm12); - - putAnimationForVertex( - endJointIndex, startJointIndex, fraction1); - putAnimationForVertex( - endJointIndex, startJointIndex, fraction2); - putAnimationForVertex( - endJointIndex, startJointIndex, fraction2); - } - } - } - - /** - * Rebuild this mesh after a parameter change. - */ - private void updateAll() { - updateDerivedProperties(); - updateBuffers(); - updateBound(); - } - - /** - * Allocate mesh buffers and fill them with data. - */ - private void updateBuffers() { - initCircleSamples(); - allocateBuffers(); - - int numJoints = armature.getJointCount(); - for (int childIndex = 0; childIndex < numJoints; ++childIndex) { - Joint child = armature.getJoint(childIndex); - Joint parent = child.getParent(); - if (parent != null) { // child is not a root joint - int parentIndex = armature.getJointIndex(parent); - Vector3f childPosition - = child.getModelTransform().getTranslation(); - Vector3f parentPosition - = parent.getModelTransform().getTranslation(); - Vector3f offset = parentPosition.subtract(childPosition); - float endZ = offset.length(); - if (endZ < FastMath.FLT_EPSILON) { - throw new IllegalStateException("Joint with length=0"); - } - - Vector3f direction = offset.clone(); - Vector3f axis1 = new Vector3f(); - Vector3f axis2 = new Vector3f(); - MyVector3f.generateBasis(direction, axis1, axis2); - Quaternion orientation = new Quaternion(); - orientation.fromAxes(axis1, axis2, direction); - - float startZ = isLeaf(child) ? -leafOvershoot : 0f; - - putTube(childPosition, orientation, startZ, endZ, childIndex, - parentIndex); - - if (capChild(child)) { - // Cap the child's end: the "start" of the current segment. - putCap(childPosition, orientation, startZ, -1f, childIndex); - } - - if (capParent(parent)) { - // Cap the parent's end: the "end" of the current segment. - putCap(childPosition, orientation, endZ, 1f, parentIndex); - } - } - } - - this.circleSamples = null; - } - - /** - * Update 2 basic properties of the mesh: triangleCount and vertexCount. - */ - private void updateDerivedProperties() { - int numSegments = 0; - int numCaps = 0; - int numJoints = armature.getJointCount(); - for (int childIndex = 0; childIndex < numJoints; ++childIndex) { - Joint child = armature.getJoint(childIndex); - Joint parent = child.getParent(); - if (parent != null) { - ++numSegments; - if (capChild(child)) { - ++numCaps; - } - if (capParent(parent)) { - ++numCaps; - } - } - } - - int trianglesPerCap = samplesPerLoop - 2; - int trianglesPerLoop = 2 * samplesPerLoop; - int trianglesPerSegment = trianglesPerLoop * loopsPerSegment; - int numTriangles - = trianglesPerCap * numCaps + trianglesPerSegment * numSegments; - logger.log(Level.INFO, "{0} triangles", numTriangles); - - this.numVertices = vpt * numTriangles; - logger.log(Level.INFO, "{0} vertices", numVertices); - assert numVertices <= Short.MAX_VALUE : numVertices; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.mesh; + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import com.jme3.util.clone.Cloner; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.BitSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyMesh; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyQuaternion; +import jme3utilities.math.MyVector3f; + +/** + * An animated Triangles-mode Mesh (with indices and normals, but no texture + * coordinates) for a branching 3-D shape that conforms to an Armature. Can be + * used to visualize ropes, hoses, snakes, and such. TODO add texture + * coordinates + * + * @author Stephen Gold sgold@sonic.net + */ +public class TubeTreeMesh extends Mesh { + // ************************************************************************* + // constants and loggers + + /** + * maximum number of weights per vertex + */ + final private static int maxWpv = 4; + /** + * number of axes in a vector + */ + final private static int numAxes = 3; + /** + * number of vertices per triangle + */ + final private static int vpt = 3; + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(TubeTreeMesh.class.getName()); + /** + * local copy of {@link com.jme3.math.Vector3f#ZERO} + */ + final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f); + // ************************************************************************* + // fields + + /** + * Armature used to construct the Mesh + */ + private Armature armature; + /** + * overshoot distance for leaf joints (in mesh units, default=0) + */ + private float leafOvershoot; + /** + * radius of each mesh loop (in mesh units) + */ + private float radius; + /** + * normal buffer + */ + private FloatBuffer normalBuffer; + /** + * weight buffer + */ + private FloatBuffer weightBuffer; + /** + * position buffer + */ + private FloatBuffer positionBuffer; + /** + * index buffer + */ + private IndexBuffer indexBuffer; + /** + * total number of vertices in the mesh + */ + private int numVertices; + /** + * number of mesh loops in each tube segment (default=3) + */ + private int loopsPerSegment; + /** + * number of sample points per mesh loop (default=12) + */ + private int samplesPerLoop; + /** + * cached sample positions for a unit circle in the X-Y plane + */ + private Vector3f[] circleSamples = null; + /** + * reusable samples to transform + */ + private Vector3f[] reusable = { + new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f(), + new Vector3f(), new Vector3f(), new Vector3f(), new Vector3f() + }; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected TubeTreeMesh() { + } + + /** + * Instantiate a tube-tree mesh based on the specified Armature and radius. + * + * @param armature (not null, in bind pose, unaffected) + * @param radius the desired radius of each mesh loop (in mesh units, >0) + */ + public TubeTreeMesh(Armature armature, float radius) { + this(armature, radius, 0f, 3, 12); + } + + /** + * Instantiate a tube-tree mesh based on the specified Armature and + * parameters. + * + * @param armature (not null, in bind pose, unaffected) + * @param radius the desired radius of each mesh loop (in mesh units, >0) + * @param leafOvershoot the desired overshoot distance for leaf joints (in + * mesh units) + * @param loopsPerSegment the desired number of mesh loops in each tube + * segment (≥1) + * @param samplesPerLoop the desired number of samples in each mesh loop + * (≥3) + */ + public TubeTreeMesh(Armature armature, float radius, float leafOvershoot, + int loopsPerSegment, int samplesPerLoop) { + Validate.nonNull(armature, "armature"); + Validate.positive(radius, "radius"); + Validate.positive(loopsPerSegment, "loops per segment"); + Validate.inRange( + samplesPerLoop, "samples per loop", 3, Integer.MAX_VALUE); + + this.armature = Heart.deepCopy(armature); + this.armature.update(); + + this.radius = radius; + this.leafOvershoot = leafOvershoot; + this.loopsPerSegment = loopsPerSegment; + this.samplesPerLoop = samplesPerLoop; + + setMaxNumWeights(2); + updateAll(); + } + // ************************************************************************* + // new methods exposed + + /** + * Enumerate the indices of all cap vertices for the named Joint. + * + * @param jointName the name of the Joint + * @return a new BitSet of vertex indices (not null) + */ + public BitSet listCapVertices(String jointName) { + Joint joint = armature.getJoint(jointName); + if (joint == null) { + String message = "no such joint: " + MyString.quote(jointName); + throw new IllegalArgumentException(message); + } + + int numJoints = armature.getJointCount(); + int trianglesPerCap = samplesPerLoop - 2; + int trianglesPerLoop = 2 * samplesPerLoop; + int trianglesPerSegment = trianglesPerLoop * loopsPerSegment; + + BitSet result = new BitSet(numVertices); + int triCount = 0; + + for (int childIndex = 0; childIndex < numJoints; ++childIndex) { + Joint child = armature.getJoint(childIndex); + Joint parent = child.getParent(); + if (parent != null) { + triCount += trianglesPerSegment; + + if (capChild(child)) { + if (child == joint) { + int fromIndex = vpt * triCount; + int toIndex = vpt * (triCount + trianglesPerCap); + result.set(fromIndex, toIndex); + } + triCount += trianglesPerCap; + } + + if (capParent(parent)) { + if (parent == joint) { + int fromIndex = vpt * triCount; + int toIndex = vpt * (triCount + trianglesPerCap); + result.set(fromIndex, toIndex); + } + triCount += trianglesPerCap; + } + } + } + + return result; + } + + /** + * Calculate the number of vertices in each cap. + * + * @return count (≥3) + */ + public int verticesPerCap() { + int trianglesPerCap = samplesPerLoop - 2; + int result = vpt * trianglesPerCap; + + return result; + } + // ************************************************************************* + // Mesh methods + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned mesh into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this mesh (not null) + * @param original the mesh from which this mesh was shallow-cloned (unused) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + + this.weightBuffer = cloner.clone(weightBuffer); + this.normalBuffer = cloner.clone(normalBuffer); + this.positionBuffer = cloner.clone(positionBuffer); + this.indexBuffer = cloner.clone(indexBuffer); + // armature not cloned (read-only) + assert circleSamples == null : circleSamples; + this.reusable = cloner.clone(reusable); + } + + /** + * De-serialize this mesh from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + this.leafOvershoot = capsule.readFloat("leafOvershoot", 0f); + this.radius = capsule.readFloat("radius", 1f); + this.loopsPerSegment = capsule.readInt("loopsPerSegment", 3); + this.samplesPerLoop = capsule.readInt("samplesPerLoop", 12); + this.armature = (Armature) capsule.readSavable("armature", null); + + // Recalculate the derived properties. + updateDerivedProperties(); + } + + /** + * Serialize this Mesh to the specified exporter, for example when saving to + * a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(leafOvershoot, "leafOvershoot", 0f); + capsule.write(radius, "radius", 1f); + capsule.write(loopsPerSegment, "loopsPerSegment", 3); + capsule.write(samplesPerLoop, "samplesPerLoop", 12); + capsule.write(armature, "armature", null); + } + // ************************************************************************* + // private methods + + /** + * Allocate new buffers for mesh data. + */ + private void allocateBuffers() { + int weightCount = maxWpv * numVertices; + this.indexBuffer + = IndexBuffer.createIndexBuffer(numVertices, weightCount); + MyMesh.setBoneIndexBuffer(this, maxWpv, indexBuffer); + this.weightBuffer + = BufferUtils.createFloatBuffer(weightCount); + setBuffer(VertexBuffer.Type.BoneWeight, maxWpv, weightBuffer); + + this.positionBuffer = BufferUtils.createVector3Buffer(numVertices); + setBuffer(VertexBuffer.Type.BindPosePosition, numAxes, positionBuffer); + this.normalBuffer = BufferUtils.createVector3Buffer(numVertices); + setBuffer(VertexBuffer.Type.BindPoseNormal, numAxes, normalBuffer); + + FloatBuffer pb = BufferUtils.createVector3Buffer(numVertices); + setBuffer(VertexBuffer.Type.Position, numAxes, pb); + FloatBuffer nb = BufferUtils.createVector3Buffer(numVertices); + setBuffer(VertexBuffer.Type.Normal, numAxes, nb); + } + + /** + * Test whether segments having the specified Joint as the child should be + * capped at the child's end. + * + * @param child the Joint to test (not null, unaffected) + * @return true if capped, otherwise false + */ + private static boolean capChild(Joint child) { + int numChildren = child.getChildren().size(); + if (numChildren == 1) { + return false; + } else { + return true; // child is a leaf or fork: cap it + } + } + + /** + * Test whether segments having the specified Joint as the parent should be + * capped at the parent's end. + * + * @param parent the Joint to test (not null, unaffected) + * @return true if capped, otherwise false + */ + private static boolean capParent(Joint parent) { + Joint grandparent = parent.getParent(); + boolean isCapped; + if (grandparent == null) { + isCapped = true; // parent is a root: cap it + } else { + int numChildren = parent.getChildren().size(); + if (numChildren > 1) { + isCapped = true; // parent is a fork: cap it + } else { + isCapped = false; + } + } + return isCapped; + } + + /** + * Pre-compute sample coordinates for a unit circle in the X-Y plane, + * centered at the origin. + */ + private void initCircleSamples() { + this.circleSamples = new Vector3f[samplesPerLoop + 1]; + for (int sampleI = 0; sampleI <= samplesPerLoop; ++sampleI) { + float theta = (FastMath.TWO_PI / samplesPerLoop) * sampleI; + float cos = FastMath.cos(theta); + float sin = FastMath.sin(theta); + this.circleSamples[sampleI] = new Vector3f(cos, sin, 0f); + } + } + + /** + * Test whether the specified Joint is a leaf. + * + * @param joint the Joint to test (not null, unaffected) + * @return true for a leaf, otherwise false + */ + private static boolean isLeaf(Joint joint) { + boolean result = joint.getChildren().isEmpty(); + return result; + } + + /** + * Write joint indices and weights for a triangle animated entirely by a + * single Joint. + * + * @param jointIndex the index of the Joint that animates the triangle + */ + private void putAnimationForTriangle(int jointIndex) { + for (int vertexIndex = 0; vertexIndex < vpt; ++vertexIndex) { + indexBuffer.put(jointIndex); + weightBuffer.put(1f); + + for (int weightIndex = 1; weightIndex < maxWpv; ++weightIndex) { + indexBuffer.put(0); + weightBuffer.put(0f); + } + } + } + + /** + * Write joint indices and weights for a vertex animated by 2 joints. + * + * @param jointIndex1 the index of the first Joint (≥0) + * @param jointIndex2 the index of the 2nd Joint (≥0) + * @param w1 the weight for the first Joint + */ + private void putAnimationForVertex( + int jointIndex1, int jointIndex2, float w1) { + float weight1 = FastMath.clamp(w1, 0f, 1f); + + int weightIndex; + if (weight1 != 0f) { + indexBuffer.put(jointIndex1); + weightBuffer.put(weight1); + weightIndex = 2; + } else { + weightIndex = 1; + } + + indexBuffer.put(jointIndex2); + weightBuffer.put(1f - weight1); + + while (weightIndex < maxWpv) { + indexBuffer.put(0); + weightBuffer.put(0f); + ++weightIndex; + } + } + + /** + * Write a flat, circular cap to the buffers. TODO more cap-shape options + * + * @param centerPos the position of the center of the cap (in mesh + * coordinates, not null, unaffected) + * @param orientation the orientation of the cap (cap lies in the X-Y plane) + * (in mesh coordinates, not null, unaffected) + * @param posZ the Z component of the cap position (in local coordinates) + * @param normalZ the Z component of the normal direction (in local + * coordinates, +1 or -1) + * @param jointIndex (≥0) + */ + private void putCap(Vector3f centerPos, Quaternion orientation, float posZ, + float normalZ, int jointIndex) { + Vector3f a = reusable[0]; + Vector3f b = reusable[1]; + Vector3f c = reusable[2]; + + int startBufferOffset = positionBuffer.position(); + + // Put a triangle for each sample point except the first and last. + for (int triIndex = 1; triIndex < samplesPerLoop - 1; ++triIndex) { + Vector3f pos1 = circleSamples[0]; + Vector3f pos2; + Vector3f pos3; + if (normalZ > 0f) { + pos2 = circleSamples[triIndex]; + pos3 = circleSamples[triIndex + 1]; + } else { + pos2 = circleSamples[triIndex + 1]; + pos3 = circleSamples[triIndex]; + } + + a.set(radius * pos1.x, radius * pos1.y, posZ); + b.set(radius * pos2.x, radius * pos2.y, posZ); + c.set(radius * pos3.x, radius * pos3.y, posZ); + putTransformedTriangle( + positionBuffer, centerPos, orientation, a, b, c); + + a.set(0f, 0f, normalZ); + b.set(0f, 0f, normalZ); + c.set(0f, 0f, normalZ); + putTransformedTriangle( + normalBuffer, translateIdentity, orientation, a, b, c); + + putAnimationForTriangle(jointIndex); + } + int numFloats = positionBuffer.position() - startBufferOffset; + assert numFloats == numAxes * verticesPerCap(); + } + + /** + * Rotate and translate each vertex of a triangle and write the transformed + * coordinates to the specified buffer. + * + * @param buffer the buffer to write to (not null, modified) + * @param offset the translation to apply (not null, unaffected) + * @param rotation the rotation to apply (not null, unaffected) + * @param v1 the first vertex vector to transform (not null, modified) + * @param v2 the 2nd vertex vector to transform (not null, modified) + * @param v3 the 3rd vertex vector to transform (not null, modified) + */ + private static void putTransformedTriangle( + FloatBuffer buffer, Vector3f offset, Quaternion rotation, + Vector3f v1, Vector3f v2, Vector3f v3) { + MyQuaternion.rotate(rotation, v1, v1); + v1.addLocal(offset); + buffer.put(v1.x).put(v1.y).put(v1.z); + + MyQuaternion.rotate(rotation, v2, v2); + v2.addLocal(offset); + buffer.put(v2.x).put(v2.y).put(v2.z); + + MyQuaternion.rotate(rotation, v3, v3); + v3.addLocal(offset); + buffer.put(v3.x).put(v3.y).put(v3.z); + } + + /** + * Write an uncapped, cylindrical tube segment to the buffers. + * + * @param origin the origin of the segment's local coordinate system (in + * mesh coordinates, not null, unaffected) + * @param orientation the orientation of the segment's local coordinate + * system (in mesh coordinates, the local +Z axis being the length axis, not + * null, unaffected) + * @param startZ the Z component of the start position (in local + * coordinates) + * @param endZ the Z component of the end position (in local coordinates) + * @param startJointIndex (≥0) + * @param endJointIndex (≥0) + */ + private void putTube(Vector3f origin, Quaternion orientation, float startZ, + float endZ, int startJointIndex, int endJointIndex) { + assert startJointIndex != endJointIndex; + + Vector3f pos11 = reusable[0]; + Vector3f pos12 = reusable[1]; + Vector3f pos21 = reusable[2]; + Vector3f pos22 = reusable[3]; + Vector3f norm11 = reusable[4]; + Vector3f norm12 = reusable[5]; + Vector3f norm21 = reusable[6]; + Vector3f norm22 = reusable[7]; + + float fractionStep = 1f / loopsPerSegment; + for (int loopIndex = 0; loopIndex < loopsPerSegment; ++loopIndex) { + float fraction1 = fractionStep * loopIndex; + float fraction2 = fractionStep * (loopIndex + 1); + float z1 = MyMath.lerp(fraction1, startZ, endZ); + float z2 = MyMath.lerp(fraction2, startZ, endZ); + + // a rectangular patch for each sample point + for (int rectIndex = 0; rectIndex < samplesPerLoop; ++rectIndex) { + float x1 = circleSamples[rectIndex].x * radius; + float y1 = circleSamples[rectIndex].y * radius; + float x2 = circleSamples[rectIndex + 1].x * radius; + float y2 = circleSamples[rectIndex + 1].y * radius; + Vector3f normal1 = circleSamples[rectIndex]; + Vector3f normal2 = circleSamples[rectIndex + 1]; + + // Put the lower-right triangle. + pos11.set(x1, y1, z1); + pos21.set(x2, y2, z1); + pos22.set(x2, y2, z2); + putTransformedTriangle(positionBuffer, origin, + orientation, pos11, pos21, pos22); + + norm11.set(normal1.x, normal1.y, 0f); + norm21.set(normal2.x, normal2.y, 0f); + norm22.set(normal2.x, normal2.y, 0f); + putTransformedTriangle(normalBuffer, translateIdentity, + orientation, norm11, norm21, norm22); + + putAnimationForVertex( + endJointIndex, startJointIndex, fraction1); + putAnimationForVertex( + endJointIndex, startJointIndex, fraction1); + putAnimationForVertex( + endJointIndex, startJointIndex, fraction2); + + // Put the upper-left triangle. + pos11.set(x1, y1, z1); + pos12.set(x1, y1, z2); + pos22.set(x2, y2, z2); + putTransformedTriangle(positionBuffer, origin, + orientation, pos11, pos22, pos12); + + norm11.set(normal1.x, normal1.y, 0f); + norm12.set(normal1.x, normal1.y, 0f); + norm22.set(normal2.x, normal2.y, 0f); + putTransformedTriangle(normalBuffer, translateIdentity, + orientation, norm11, norm22, norm12); + + putAnimationForVertex( + endJointIndex, startJointIndex, fraction1); + putAnimationForVertex( + endJointIndex, startJointIndex, fraction2); + putAnimationForVertex( + endJointIndex, startJointIndex, fraction2); + } + } + } + + /** + * Rebuild this mesh after a parameter change. + */ + private void updateAll() { + updateDerivedProperties(); + updateBuffers(); + updateBound(); + } + + /** + * Allocate mesh buffers and fill them with data. + */ + private void updateBuffers() { + initCircleSamples(); + allocateBuffers(); + + int numJoints = armature.getJointCount(); + for (int childIndex = 0; childIndex < numJoints; ++childIndex) { + Joint child = armature.getJoint(childIndex); + Joint parent = child.getParent(); + if (parent != null) { // child is not a root joint + int parentIndex = armature.getJointIndex(parent); + Vector3f childPosition + = child.getModelTransform().getTranslation(); + Vector3f parentPosition + = parent.getModelTransform().getTranslation(); + Vector3f offset = parentPosition.subtract(childPosition); + float endZ = offset.length(); + if (endZ < FastMath.FLT_EPSILON) { + throw new IllegalStateException("Joint with length=0"); + } + + Vector3f direction = offset.clone(); + Vector3f axis1 = new Vector3f(); + Vector3f axis2 = new Vector3f(); + MyVector3f.generateBasis(direction, axis1, axis2); + Quaternion orientation = new Quaternion(); + orientation.fromAxes(axis1, axis2, direction); + + float startZ = isLeaf(child) ? -leafOvershoot : 0f; + + putTube(childPosition, orientation, startZ, endZ, childIndex, + parentIndex); + + if (capChild(child)) { + // Cap the child's end: the "start" of the current segment. + putCap(childPosition, orientation, startZ, -1f, childIndex); + } + + if (capParent(parent)) { + // Cap the parent's end: the "end" of the current segment. + putCap(childPosition, orientation, endZ, 1f, parentIndex); + } + } + } + + this.circleSamples = null; + } + + /** + * Update 2 basic properties of the mesh: triangleCount and vertexCount. + */ + private void updateDerivedProperties() { + int numSegments = 0; + int numCaps = 0; + int numJoints = armature.getJointCount(); + for (int childIndex = 0; childIndex < numJoints; ++childIndex) { + Joint child = armature.getJoint(childIndex); + Joint parent = child.getParent(); + if (parent != null) { + ++numSegments; + if (capChild(child)) { + ++numCaps; + } + if (capParent(parent)) { + ++numCaps; + } + } + } + + int trianglesPerCap = samplesPerLoop - 2; + int trianglesPerLoop = 2 * samplesPerLoop; + int trianglesPerSegment = trianglesPerLoop * loopsPerSegment; + int numTriangles + = trianglesPerCap * numCaps + trianglesPerSegment * numSegments; + logger.log(Level.INFO, "{0} triangles", numTriangles); + + this.numVertices = vpt * numTriangles; + logger.log(Level.INFO, "{0} vertices", numVertices); + assert numVertices <= Short.MAX_VALUE : numVertices; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/package-info.java index 1294f0fc9..3606e2fec 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/mesh/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2019, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes that generate meshes used in the MinieExamples subproject. - */ -package jme3utilities.minie.test.mesh; +/* + Copyright (c) 2019, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes that generate meshes used in the MinieExamples subproject. + */ +package jme3utilities.minie.test.mesh; diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/package-info.java index b3a5354c2..6d8f48b7a 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2018, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Applications to test and/or demonstrate capabilities of Minie. - */ -package jme3utilities.minie.test; +/* + Copyright (c) 2018, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Applications to test and/or demonstrate capabilities of Minie. + */ +package jme3utilities.minie.test; diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/CompoundTestShapes.java b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/CompoundTestShapes.java index 3e46e0331..4d04ab6d6 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/CompoundTestShapes.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/CompoundTestShapes.java @@ -1,980 +1,980 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shape; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SimplexCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Plane; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.Validate; -import jme3utilities.math.MyMath; -import jme3utilities.math.RectangularSolid; -import jme3utilities.mesh.Octasphere; -import jme3utilities.minie.MyShape; -import jme3utilities.minie.test.mesh.StarSlice; - -/** - * Utility class to generate compound collision shapes for use in MinieExamples. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class CompoundTestShapes { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(CompoundTestShapes.class.getName()); - // ************************************************************************* - // fields - - /** - * inverse moment-of-inertia vector for a "bowl" shape with mass=1, scale=1 - * (initialized by makeBowl() - */ - public static Vector3f bowlInverseInertia = null; - /** - * inverse moment-of-inertia vector for a "chair" shape with mass=1, scale=1 - * (initialized by makeChair() - */ - public static Vector3f chairInverseInertia = null; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private CompoundTestShapes() { - } - // ************************************************************************* - // new methods exposed - - /** - * Generate a barbell shape with 2 cylindrical plates. - * - * @param barLength the total length of the bar (X axis, in unscaled shape - * units, >0) - * @param barRadius the radius of the bar (in unscaled shape units, >0) - * @param plateRadius the radius of each plate (in unscaled shape units, - * >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeBarbell( - float barLength, float barRadius, float plateRadius) { - Validate.positive(barLength, "bar length"); - Validate.positive(barRadius, "bar radius"); - Validate.positive(plateRadius, "plate radius"); - - float plateOffset = 0.42f * barLength; - CollisionShape bar = new CylinderCollisionShape( - barRadius, barLength, PhysicsSpace.AXIS_X); - - float plateThickness = 0.08f * barLength; - CollisionShape plate = new CylinderCollisionShape( - plateRadius, plateThickness, PhysicsSpace.AXIS_X); - - CompoundCollisionShape result = new CompoundCollisionShape(3); - result.addChildShape(bar); - result.addChildShape(plate, -plateOffset, 0f, 0f); - result.addChildShape(plate, plateOffset, 0f, 0f); - - return result; - } - - /** - * Approximate a hemispherical shell (or bowl), open on the +Z side, using - * 3-sphere shapes. - * - * @param innerRadius (in unscaled shape units, >thickness) - * @param thickness (in unscaled shape units, >0, <innerRadius) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeBowl( - float innerRadius, float thickness) { - Validate.inRange( - innerRadius, "inner radius", thickness, Float.MAX_VALUE); - Validate.inRange(thickness, "thickness", 0f, innerRadius); - - float halfThickness = thickness / 2f; - float midRadius = innerRadius + halfThickness; - int numRefineSteps = 2; - Octasphere mesh = new Octasphere(numRefineSteps, midRadius); - int numTriangles = mesh.getTriangleCount(); - - List radii = new ArrayList<>(3); - radii.add(thickness); - radii.add(thickness); - radii.add(thickness); - List centers = new ArrayList<>(3); - Vector3f v1 = new Vector3f(); - Vector3f v2 = new Vector3f(); - Vector3f v3 = new Vector3f(); - centers.add(v1); - centers.add(v2); - centers.add(v3); - Vector3f centroid = new Vector3f(); - float maxZ = 1e-4f; - CompoundCollisionShape result - = new CompoundCollisionShape(numTriangles / 2); - - for (int triangleI = 0; triangleI < numTriangles; ++triangleI) { - mesh.getTriangle(triangleI, v1, v2, v3); - if (v1.z < maxZ && v2.z < maxZ && v3.z < maxZ) { - centroid.zero(); - centroid.addLocal(v1).addLocal(v2).addLocal(v3); - centroid.divideLocal(3f); - - v1.subtractLocal(centroid); - v2.subtractLocal(centroid); - v3.subtractLocal(centroid); - - MultiSphere triSphere = new MultiSphere(centers, radii); - result.addChildShape(triSphere, centroid); - } - } - - int numChildren = result.countChildren(); - FloatBuffer masses = BufferUtils.createFloatBuffer(numChildren); - for (int childIndex = 0; childIndex < numChildren; ++childIndex) { - masses.put(1f / numChildren); - } - Vector3f inertia = new Vector3f(); - Transform transform - = result.principalAxes(masses, null, inertia); - bowlInverseInertia = Vector3f.UNIT_XYZ.divide(inertia); - result.correctAxes(transform); - - return result; - } - - /** - * Generate a chair with 4 cylindrical legs (asymmetrical). Must override - * the moments of inertia if used in a dynamic body. - * - * @param backLength the length of the back (Y axis, in unscaled shape - * units, >0) - * @param legLength the length of each leg (Y axis, in unscaled shape units, - * >0) - * @param legOffset the offset of each leg from the center of the seat (in - * unscaled shape units, >legRadius) - * @param legRadius the radius of each leg (in unscaled shape units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeChair(float backLength, - float legLength, float legOffset, float legRadius) { - Validate.positive(backLength, "back length"); - Validate.positive(legLength, "leg length"); - Validate.inRange(legOffset, "leg offset", legRadius, Float.MAX_VALUE); - Validate.positive(legRadius, "leg radius"); - - float seatHalf = legOffset + legRadius; - Vector3f halfExtents = new Vector3f(seatHalf, 0.2f, seatHalf); - RectangularSolid solid = new RectangularSolid(halfExtents); - CollisionShape seat = new MultiSphere(solid); - - CollisionShape frontLeg = new CylinderCollisionShape( - legRadius, legLength, PhysicsSpace.AXIS_Y); - - float rearLength = legLength + backLength; - CollisionShape rearLeg = new CylinderCollisionShape( - legRadius, rearLength, PhysicsSpace.AXIS_Y); - - float rearHalf = rearLength / 2f; - float legHalf = legLength / 2f; - float backHalf = backLength / 2f; - halfExtents.set(legOffset, backHalf, legRadius); - solid = new RectangularSolid(halfExtents); - CollisionShape back = new MultiSphere(solid); - - CompoundCollisionShape result = new CompoundCollisionShape(6); - result.addChildShape(seat); - result.addChildShape(frontLeg, legOffset, -legHalf, legOffset); - result.addChildShape(frontLeg, -legOffset, -legHalf, legOffset); - float yOffset = rearHalf - legLength; - result.addChildShape(rearLeg, legOffset, yOffset, -legOffset); - result.addChildShape(rearLeg, -legOffset, yOffset, -legOffset); - result.addChildShape(back, 0f, backHalf, -legOffset); - - float[] volumes = MyShape.listVolumes(result); - float sum = 0f; - for (float volume : volumes) { - sum += volume; - } - FloatBuffer masses = BufferUtils.createFloatBuffer(volumes.length); - for (float volume : volumes) { - masses.put(volume / sum); - } - Vector3f inertia = new Vector3f(); - Transform transform = result.principalAxes(masses, null, inertia); - chairInverseInertia = Vector3f.UNIT_XYZ.divide(inertia); - result.correctAxes(transform); - - return result; - } - - /** - * Generate an inverted hollow pyramid with its nadir on the -Y axis. Not - * intended for use in a dynamic body. TODO use MeshCollisionShape - * - * @param numSides the number of sides (≥3) - * @param rimRadius (in unscaled shape units, >0) - * @param depth rimY minus nadirY (in unscaled shape units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeCorner( - int numSides, float rimRadius, float depth) { - Validate.inRange(numSides, "number of sides", 3, Integer.MAX_VALUE); - Validate.positive(rimRadius, "rim radius"); - Validate.positive(depth, "depth"); - - float stepAngle = FastMath.TWO_PI / numSides; // in radians - Matrix3f rotationMatrix = new Matrix3f(); - rotationMatrix.fromAngleAxis(stepAngle, Vector3f.UNIT_Y); - - float rimY = 0.3f * depth; - Vector3f vertex1 = new Vector3f(rimRadius, rimY, 0f); - Vector3f vertex2 = rotationMatrix.mult(vertex1, null); - Vector3f nadir = new Vector3f(0f, rimY - depth, 0f); - SimplexCollisionShape triangle - = new SimplexCollisionShape(vertex1, vertex2, nadir); - - CompoundCollisionShape result = new CompoundCollisionShape(numSides); - result.addChildShape(triangle); - result.addChildShape(triangle, Vector3f.ZERO, rotationMatrix); - for (int i = 2; i < numSides; ++i) { - float angle = stepAngle * i; - rotationMatrix.fromAngleAxis(angle, Vector3f.UNIT_Y); - result.addChildShape(triangle, Vector3f.ZERO, rotationMatrix); - } - - return result; - } - - /** - * Generate a rectangular frame, open on the Z axis. - * - * @param ihHeight half of the internal height (Y direction, in unscaled - * shape units, >0) - * @param ihWidth half of the internal width (X direction, in unscaled shape - * units, >0) - * @param halfDepth half of the (external) depth (Z direction, in unscaled - * shape units, >0) - * @param halfThickness half the thickness (in unscaled shape units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeFrame(float ihHeight, - float ihWidth, float halfDepth, float halfThickness) { - Validate.positive(ihHeight, "half height"); - Validate.positive(ihWidth, "half width"); - Validate.positive(halfDepth, "half depth"); - Validate.positive(halfThickness, "half thickness"); - - float mhHeight = ihHeight + halfThickness; - float mhWidth = ihWidth + halfThickness; - - CollisionShape horizontal - = new BoxCollisionShape(mhWidth, halfThickness, halfDepth); - CollisionShape vertical - = new BoxCollisionShape(halfThickness, mhHeight, halfDepth); - - CompoundCollisionShape result = new CompoundCollisionShape(4); - result.addChildShape(horizontal, halfThickness, -mhHeight, 0f); - result.addChildShape(horizontal, -halfThickness, mhHeight, 0f); - result.addChildShape(vertical, mhWidth, halfThickness, 0f); - result.addChildShape(vertical, -mhWidth, -halfThickness, 0f); - - return result; - } - - /** - * Generate an I-beam. - * - * @param length (Z axis, in unscaled shape units, >0) - * @param flangeWidth (X axis, in unscaled shape units, ≥thickness) - * @param beamHeight (Y axis, in unscaled shape units, ≥2*thickness) - * @param thickness (in unscaled shape units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeIBeam(float length, - float flangeWidth, float beamHeight, float thickness) { - Validate.positive(length, "length"); - Validate.positive(thickness, "thickness"); - Validate.inRange( - flangeWidth, "flange width", thickness, Float.MAX_VALUE); - Validate.inRange( - beamHeight, "beam height", 2f * thickness, Float.MAX_VALUE); - - float halfLength = length / 2f; - float halfThickness = thickness / 2f; - float webHalfHeight = beamHeight / 2f - thickness; - CollisionShape web = new BoxCollisionShape( - halfThickness, webHalfHeight, halfLength); - CollisionShape flange = new BoxCollisionShape( - flangeWidth / 2f, halfThickness, halfLength); - - CompoundCollisionShape result = new CompoundCollisionShape(3); - result.addChildShape(web); - float flangeY = webHalfHeight + halfThickness; - result.addChildShape(flange, 0f, flangeY, 0f); - result.addChildShape(flange, 0f, -flangeY, 0f); - - return result; - } - - /** - * Generate a knucklebone with 4 spherical balls. (No balls on the Z axis.) - * - * @param stemLength the length of each stem (in unscaled shape units, - * >0) - * @param stemRadius the radius of each stem (in unscaled shape units, - * >0) - * @param ballRadius the radius of each ball (in unscaled shape units, - * >stemRadius) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeKnucklebone( - float stemLength, float stemRadius, float ballRadius) { - Validate.positive(stemLength, "stem length"); - Validate.positive(stemRadius, "stem radius"); - Validate.positive(ballRadius, "ball radius"); - - CollisionShape xStem = new CapsuleCollisionShape( - stemRadius, stemLength, PhysicsSpace.AXIS_X); - CollisionShape yStem = new CapsuleCollisionShape( - stemRadius, stemLength, PhysicsSpace.AXIS_Y); - CollisionShape zStem = new CapsuleCollisionShape( - stemRadius, stemLength, PhysicsSpace.AXIS_Z); - - CollisionShape ball = new SphereCollisionShape(ballRadius); - - CompoundCollisionShape result = new CompoundCollisionShape(7); - result.addChildShape(xStem); - result.addChildShape(yStem); - result.addChildShape(zStem); - - float stemHalf = stemLength / 2f; - result.addChildShape(ball, stemHalf, 0f, 0f); - result.addChildShape(ball, -stemHalf, 0f, 0f); - result.addChildShape(ball, 0f, stemHalf, 0f); - result.addChildShape(ball, 0f, -stemHalf, 0f); - - return result; - } - - /** - * Generate a ladder with 5 cylindrical rungs. - * - * @param rungLength the total length of each rung (X direction, in unscaled - * shape units, >0) - * @param rungSpacing the spacing between rungs (Y direction, in unscaled - * shape units, >2*rungRadius) - * @param rungRadius the radius of each rung (in unscaled shape units, - * >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeLadder( - float rungLength, float rungSpacing, float rungRadius) { - Validate.positive(rungLength, "rung length"); - Validate.inRange( - rungSpacing, "rung spacing", 2f * rungRadius, Float.MAX_VALUE); - Validate.positive(rungRadius, "rung radius"); - - CollisionShape rung = new CylinderCollisionShape( - rungRadius, rungLength, PhysicsSpace.AXIS_X); - - float railLength = 6f * rungSpacing; - float railHalf = railLength / 2f; - CollisionShape rail - = new BoxCollisionShape(rungRadius, railHalf, rungRadius); - - CompoundCollisionShape result = new CompoundCollisionShape(7); - result.addChildShape(rung, 0f, 2f * rungSpacing, 0f); - result.addChildShape(rung, 0f, rungSpacing, 0f); - result.addChildShape(rung); - result.addChildShape(rung, 0f, -rungSpacing, 0f); - result.addChildShape(rung, 0f, -2f * rungSpacing, 0f); - - float rungHalf = rungLength / 2f; - result.addChildShape(rail, rungHalf, 0f, 0f); - result.addChildShape(rail, -rungHalf, 0f, 0f); - - return result; - } - - /** - * Generate a lidless rectangular box with its opening on the +Z side. - * - * @param iHeight the internal height (Y direction, in unscaled shape units, - * >0) - * @param iWidth the internal width (X direction, in unscaled shape units, - * >0) - * @param iDepth the internal depth (Z direction, in unscaled shape units, - * >0) - * @param wallThickness (in unscaled shape units, >0) - * - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeLidlessBox( - float iHeight, float iWidth, float iDepth, float wallThickness) { - Validate.positive(iHeight, "internal height"); - Validate.positive(iWidth, "internal width"); - Validate.positive(iDepth, "internal depth"); - Validate.positive(wallThickness, "wall thickness"); - - float ihHeight = iHeight / 2f; - float ihWidth = iWidth / 2f; - float ihDepth = iDepth / 2f; - float halfThickness = wallThickness / 2f; - - float fhDepth = ihDepth + halfThickness; - CompoundCollisionShape result - = makeFrame(ihHeight, ihWidth, fhDepth, halfThickness); - - BoxCollisionShape bottom - = new BoxCollisionShape(ihWidth, ihHeight, halfThickness); - result.addChildShape(bottom, 0f, 0f, -ihDepth); - - return result; - } - - /** - * Generate a rectangular link for a chain. - * - * @param ihHeight half of the internal height (Y direction, in unscaled - * shape units, >0) - * @param ihWidth half of the internal width (X direction, in unscaled shape - * units, >0) - * @param radius half the thickness (in unscaled shape units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeLink( - float ihHeight, float ihWidth, float radius) { - Validate.positive(ihHeight, "half height"); - Validate.positive(ihWidth, "half width"); - Validate.positive(radius, "radius"); - - float mhHeight = ihHeight + radius; - float mhWidth = ihWidth + radius; - - CollisionShape horizontal = new CapsuleCollisionShape( - radius, 2f * mhWidth, PhysicsSpace.AXIS_X); - CollisionShape vertical = new CapsuleCollisionShape( - radius, 2f * mhHeight, PhysicsSpace.AXIS_Y); - - CompoundCollisionShape result = new CompoundCollisionShape(4); - result.addChildShape(horizontal, 0f, -mhHeight, 0f); - result.addChildShape(horizontal, 0f, mhHeight, 0f); - result.addChildShape(vertical, mhWidth, 0f, 0f); - result.addChildShape(vertical, -mhWidth, 0f, 0f); - - return result; - } - - /** - * Generate a mad mallet by compounding 2 cylinders. - * - * @param handleR the radius of the handle (in unscaled shape units, >0) - * @param headR the radius of the head (in unscaled shape units, >0) - * @param handleHalfLength half the length of the handle (in unscaled shape - * units, >0) - * @param headHalfLength half the length of the head (in unscaled shape - * units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeMadMallet(float handleR, - float headR, float handleHalfLength, float headHalfLength) { - Validate.positive(handleR, "handle radius"); - Validate.positive(headR, "head radius"); - Validate.positive(handleHalfLength, "handle half length"); - Validate.positive(headHalfLength, "head half length"); - - Vector3f hes = new Vector3f(headR, headR, headHalfLength); - CollisionShape head = new CylinderCollisionShape(hes); - - hes.set(handleR, handleR, handleHalfLength); - CollisionShape handle = new CylinderCollisionShape(hes); - - CompoundCollisionShape result = new CompoundCollisionShape(2); - - result.addChildShape(handle, 0f, 0f, handleHalfLength); - - Vector3f offset = new Vector3f(0f, 0f, 2f * handleHalfLength); - Matrix3f rotation = new Matrix3f(); - rotation.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X); - result.addChildShape(head, offset, rotation); - - return result; - } - - /** - * Approximate an arc of a straight, square-ended pipe (or of a flat ring), - * open on the Z axis, using hulls. - * - * @param innerR the inner radius of an X-Y cross section (in unscaled shape - * units, >0) - * @param thickness the thickness of the pipe (in unscaled shape units, - * >0) - * @param zLength the length of the pipe (Z direction, in unscaled shape - * units, >0) - * @param arc the arc amount (in radians, >0, ≤2pi) - * @param numChildren the number of child shapes to create (≥3) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makePipe(float innerR, float thickness, - float zLength, float arc, int numChildren) { - Validate.positive(innerR, "inner radius"); - Validate.positive(thickness, "thickness"); - Validate.positive(zLength, "length"); - Validate.inRange(arc, "arc", 0f, FastMath.TWO_PI); - Validate.inRange( - numChildren, "number of children", 3, Integer.MAX_VALUE); - - float halfLength = zLength / 2f; - float outerR = innerR + thickness; - float segmentAngle = arc / numChildren; // in radians - - float xOff; - float yOff; // TODO more accurate centering - if (arc < 2) { - float cos = FastMath.cos(segmentAngle); - float sin = FastMath.sin(segmentAngle); - xOff = (1 + cos * outerR) / 2f; - yOff = sin * innerR / 2f; - - } else if (arc < 4) { - xOff = 0f; - yOff = outerR / 2f; - - } else { - xOff = 0f; - yOff = 0f; - } - - CompoundCollisionShape result = new CompoundCollisionShape(numChildren); - for (int segmentI = 0; segmentI < numChildren; ++segmentI) { - float theta1 = segmentI * segmentAngle; - float theta2 = (segmentI + 1) * segmentAngle; - float cos1 = FastMath.cos(theta1); - float cos2 = FastMath.cos(theta2); - float sin1 = FastMath.sin(theta1); - float sin2 = FastMath.sin(theta2); - - FloatBuffer buffer = BufferUtils.createFloatBuffer( - innerR * cos1 - xOff, innerR * sin1 - yOff, halfLength, - innerR * cos2 - xOff, innerR * sin2 - yOff, halfLength, - outerR * cos1 - xOff, outerR * sin1 - yOff, halfLength, - outerR * cos2 - xOff, outerR * sin2 - yOff, halfLength, - innerR * cos1 - xOff, innerR * sin1 - yOff, -halfLength, - innerR * cos2 - xOff, innerR * sin2 - yOff, -halfLength, - outerR * cos1 - xOff, outerR * sin1 - yOff, -halfLength, - outerR * cos2 - xOff, outerR * sin2 - yOff, -halfLength - ); - HullCollisionShape child = new HullCollisionShape(buffer); - result.addChildShape(child); - } - - return result; - } - - /** - * Generate a wire sieve, backed by a horizontal plane. Not intended for use - * in a dynamic body. - * - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeSieve() { - float wireSpacing = 2f; - float yHeight = 7f; - int numXWires = 16; - int numZWires = 16; - - float xHalfExtent = (numZWires - 1) * wireSpacing / 2f; - float zHalfExtent = (numXWires - 1) * wireSpacing / 2f; - CompoundCollisionShape result - = new CompoundCollisionShape(numXWires + numZWires + 1); - - // Add numXWires wires parallel to the X axis. - Vector3f p0 = new Vector3f(-xHalfExtent, 0f, 0f); - Vector3f p1 = new Vector3f(xHalfExtent, 0f, 0f); - SimplexCollisionShape xWire = new SimplexCollisionShape(p0, p1); - - for (int zIndex = 0; zIndex < numXWires; ++zIndex) { - float z = -zHalfExtent + wireSpacing * zIndex; - result.addChildShape(xWire, 0f, yHeight, z); - } - - // Add numZWires wires parallel to the Z axis. - p0.set(0f, 0f, -zHalfExtent); - p1.set(0f, 0f, zHalfExtent); - SimplexCollisionShape zWire = new SimplexCollisionShape(p0, p1); - - for (int xIndex = 0; xIndex < numZWires; ++xIndex) { - float x = -xHalfExtent + wireSpacing * xIndex; - result.addChildShape(zWire, x, yHeight, 0f); - } - - // Add a plane to catch any drops that pass through the sieve. - Plane plane = new Plane(Vector3f.UNIT_Y, 0f); - PlaneCollisionShape pcs = new PlaneCollisionShape(plane); - result.addChildShape(pcs); - - return result; - } - - /** - * Generate a 3-ball snowman with its head on the +Y axis. - * - * @param baseRadius (in unscaled shape units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeSnowman(float baseRadius) { - Validate.positive(baseRadius, "base radius"); - - float verticalAngle = 2f; // radians - HullCollisionShape base - = MinieTestShapes.makeDome(baseRadius, verticalAngle); - - float torsoRadius = 0.8f * baseRadius; - SphereCollisionShape torso = new SphereCollisionShape(torsoRadius); - - float headRadius = 0.6f * baseRadius; - SphereCollisionShape head = new SphereCollisionShape(headRadius); - - CompoundCollisionShape result = new CompoundCollisionShape(3); - result.addChildShape(base, 0f, -0.5f * baseRadius, 0f); - result.addChildShape(torso, 0f, 0.7f * torsoRadius, 0f); - result.addChildShape(head, 0f, 2.1f * torsoRadius, 0f); - - return result; - } - - /** - * Generate a star shape. - * - * @param numPoints the number of points (≥2) - * @param outerRadius the outer radius (in unscaled shape units, >0) - * @param centerY the half thickness at the center (>0) - * @param radiusRatio the inner radius divided by the outer radius (>0, - * <1) - * @param trianglesPerSlice the number of mesh triangles per slice (4 or 6) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeStar( - int numPoints, float outerRadius, float centerY, float radiusRatio, - int trianglesPerSlice) { - Validate.inRange(numPoints, "number of points", 2, Integer.MAX_VALUE); - Validate.positive(outerRadius, "outer radius"); - Validate.positive(centerY, "center Y"); - Validate.fraction(radiusRatio, "radius ratio"); - Validate.inRange(trianglesPerSlice, "triangles per slice", 4, 6); - - float innerRadius = radiusRatio * outerRadius; - float sliceAngle = FastMath.TWO_PI / numPoints; // in radians - boolean normals = false; - StarSlice sliceMesh = new StarSlice(sliceAngle, innerRadius, - outerRadius, 2f * centerY, normals, trianglesPerSlice); - CollisionShape sliceShape = new HullCollisionShape(sliceMesh); - - CompoundCollisionShape result = new CompoundCollisionShape(numPoints); - Matrix3f rotate = new Matrix3f(); - for (int pointIndex = 0; pointIndex < numPoints; ++pointIndex) { - rotate.fromAngleAxis(sliceAngle * pointIndex, Vector3f.UNIT_Y); - result.addChildShape(sliceShape, Vector3f.ZERO, rotate); - } - - return result; - } - - /** - * Generate a round pedestal table with its top on the +Y axis. - * - * @param topRadius the radius of the top (in unscaled shape units, - * >pedestalRadius) - * @param pedestalRadius the radius of the pedestal (in unscaled shape - * units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeTable( - float topRadius, float pedestalRadius) { - Validate.inRange( - topRadius, "top radius", pedestalRadius, Float.MAX_VALUE); - Validate.positive(pedestalRadius, "pedestal radius"); - - float thickness = 0.4f; - float footHalfLength = 0.6f * (topRadius + pedestalRadius); - CollisionShape foot - = new BoxCollisionShape(thickness / 2f, 0.25f, footHalfLength); - - float pedestalHeight = 4f; - CollisionShape pedestal = new CylinderCollisionShape( - pedestalRadius, pedestalHeight, PhysicsSpace.AXIS_Y); - - CollisionShape top = new CylinderCollisionShape( - topRadius, thickness, PhysicsSpace.AXIS_Y); - - CompoundCollisionShape result = new CompoundCollisionShape(4); - result.addChildShape(pedestal, 0f, -1f, 0f); - result.addChildShape(top, 0f, 1.2f, 0f); - - Vector3f footOffset = new Vector3f(0f, -2.75f, 0f); - result.addChildShape(foot, footOffset); - - Matrix3f rotY90 = new Matrix3f(); - rotY90.fromAngleAxis(FastMath.HALF_PI, new Vector3f(0f, 1f, 0f)); - result.addChildShape(foot, footOffset, rotY90); - - return result; - } - - /** - * Generate a thumb tack (drawing pin) with a round, flat head on the +Y - * axis. - * - * @param headRadius the radius of the head (in unscaled units, - * >spikeRadius) - * @param spikeRadius the radius of the spike (in unscaled units, >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeThumbTack( - float headRadius, float spikeRadius) { - Validate.inRange( - headRadius, "head radius", spikeRadius, Float.MAX_VALUE); - Validate.positive(spikeRadius, "spike radius"); - - float headThickness = 0.4f; - CollisionShape head = new CylinderCollisionShape( - headRadius, headThickness, PhysicsSpace.AXIS_Y); - - float spikeHeight = 3f; - CollisionShape spike = new ConeCollisionShape( - spikeRadius, spikeHeight, PhysicsSpace.AXIS_Y); - - CompoundCollisionShape result = new CompoundCollisionShape(2); - result.addChildShape(head, 0f, -0.5f, 0f); - result.addChildShape(spike, 0f, 1f, 0f); - - return result; - } - - /** - * Generate a top with a cylindrical body, its handle on the +Y axis. - * - * @param bodyRadius the radius of the body (in unscaled shape units, - * >handleRadius) - * @param handleRadius the radius of the handle (in unscaled shape units, - * >0) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeTop( - float bodyRadius, float handleRadius) { - Validate.inRange( - bodyRadius, "body radius", handleRadius, Float.MAX_VALUE); - Validate.positive(handleRadius, "handle radius"); - - float bodyHeight = 0.6f; - CollisionShape body = new CylinderCollisionShape( - bodyRadius, bodyHeight, PhysicsSpace.AXIS_Y); - - float coneHeight = 1.5f; - CollisionShape cone = new ConeCollisionShape( - bodyRadius - 0.06f, coneHeight, PhysicsSpace.AXIS_Y); - - float handleHeight = 1.5f; - CollisionShape handle = new CapsuleCollisionShape( - handleRadius, handleHeight, PhysicsSpace.AXIS_Y); - - CompoundCollisionShape result = new CompoundCollisionShape(3); - result.addChildShape(body); - float yOffset = (coneHeight + bodyHeight) / 2f; - result.addChildShape(cone, 0f, yOffset, 0f); - yOffset = -0.5f * (handleHeight + bodyHeight); - result.addChildShape(handle, 0f, yOffset, 0f); - - return result; - } - - /** - * Approximate a torus (or donut), open on the Z axis, using capsules - * arranged in a circle. - * - * @param majorRadius (in unscaled shape units, >minorRadius) - * @param minorRadius (in unscaled shape units, >0, <majorRadius) - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape - makeTorus(float majorRadius, float minorRadius) { - Validate.inRange( - majorRadius, "major radius", minorRadius, Float.MAX_VALUE); - Validate.inRange( - minorRadius, "minor radius", Float.MIN_VALUE, majorRadius); - - int numCapsules = 20; - float angle = FastMath.TWO_PI / numCapsules; - float length = majorRadius * angle; - CollisionShape capsule = new CapsuleCollisionShape( - minorRadius, length, PhysicsSpace.AXIS_X); - - CompoundCollisionShape result = new CompoundCollisionShape(numCapsules); - Vector3f offset = new Vector3f(); - Matrix3f rotation = new Matrix3f(); - - for (int childI = 0; childI < numCapsules; ++childI) { - float theta = angle * childI; - offset.x = majorRadius * FastMath.sin(theta); - offset.y = majorRadius * FastMath.cos(theta); - rotation.fromAngleNormalAxis(-theta, Vector3f.UNIT_Z); - - result.addChildShape(capsule, offset, rotation); - } - - return result; - } - - /** - * Generate a square tray with a central deflector. Not intended for use in - * a dynamic body. - * - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape makeTray() { - float iHeight = 24f; - float iWidth = 24f; - float iDepth = 3f; - float wallThickness = 3f; - CompoundCollisionShape result - = makeLidlessBox(iHeight, iWidth, iDepth, wallThickness); - - Matrix3f rotMatrix = new Matrix3f(); - rotMatrix.fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_X); - result.rotate(rotMatrix); - - float baseY = -1.5f; - Vector3f depress = new Vector3f(0f, baseY, 0f); - result.translate(depress); - - // Place a tetrahedral deflector in the center. - float size = 3f; - Vector3f p1 = new Vector3f(0f, size, 0f); - Vector3f p2 = new Vector3f(-size, baseY, size); - Vector3f p3 = new Vector3f(-size, baseY, -size); - Vector3f p4 = new Vector3f(size * MyMath.root2, baseY, 0f); - CollisionShape child = new SimplexCollisionShape(p1, p2, p3, p4); - result.addChildShape(child); - - return result; - } - - /** - * Generate a trident. - * - * @param shaftLength (Y direction, in unscaled shape units, >0) - * @param shaftRadius (in unscaled shape units, >0) - * - * @return a new compound shape (not null) - */ - public static CompoundCollisionShape - makeTrident(float shaftLength, float shaftRadius) { - Validate.positive(shaftLength, "shaft length"); - Validate.positive(shaftRadius, "shaft radius"); - - // Create a cylinder for the shaft. - CollisionShape shaft = new CylinderCollisionShape( - shaftRadius, shaftLength, PhysicsSpace.AXIS_Y); - float shaftOffset = 0.2f * shaftLength; - - // Create a box for the crosspiece. - float halfCross = 5f * shaftRadius; - float halfThickness = 0.75f * shaftRadius; - float margin = CollisionShape.getDefaultMargin(); - CollisionShape crosspiece = new BoxCollisionShape(halfCross + margin, - halfThickness + margin, halfThickness + margin); - - // Create pyramidal hulls for each of the 3 prongs. - float baseX = halfCross - halfThickness; - float pointX = halfCross + 2f * halfThickness; - float crossOffset - = shaftLength / 2f - shaftOffset + halfThickness + margin; - float baseY = crossOffset + halfThickness; - float sideY = baseY + 3f * halfCross; - float mainY = baseY + 4f * halfCross; - float[] array1 = { - halfCross, baseY, +halfThickness, - halfCross, baseY, -halfThickness, - baseX, baseY, +halfThickness, - baseX, baseY, -halfThickness, - pointX, sideY, 0f - }; - CollisionShape rightProng = new HullCollisionShape(array1); - - float[] array2 = { - -halfThickness, baseY, +halfThickness, - -halfThickness, baseY, -halfThickness, - +halfThickness, baseY, +halfThickness, - +halfThickness, baseY, -halfThickness, - 0f, mainY, 0f - }; - CollisionShape middleProng = new HullCollisionShape(array2); - - float[] array3 = { - -halfCross, baseY, +halfThickness, - -halfCross, baseY, -halfThickness, - -baseX, baseY, +halfThickness, - -baseX, baseY, -halfThickness, - -pointX, sideY, 0f - }; - CollisionShape leftProng = new HullCollisionShape(array3); - - CompoundCollisionShape result = new CompoundCollisionShape(5); - result.addChildShape(shaft, 0f, -shaftOffset, 0f); - result.addChildShape(crosspiece, 0f, crossOffset, 0f); - result.addChildShape(rightProng); - result.addChildShape(middleProng); - result.addChildShape(leftProng); - - return result; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shape; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.ConeCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SimplexCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Plane; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.math.MyMath; +import jme3utilities.math.RectangularSolid; +import jme3utilities.mesh.Octasphere; +import jme3utilities.minie.MyShape; +import jme3utilities.minie.test.mesh.StarSlice; + +/** + * Utility class to generate compound collision shapes for use in MinieExamples. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class CompoundTestShapes { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(CompoundTestShapes.class.getName()); + // ************************************************************************* + // fields + + /** + * inverse moment-of-inertia vector for a "bowl" shape with mass=1, scale=1 + * (initialized by makeBowl() + */ + public static Vector3f bowlInverseInertia = null; + /** + * inverse moment-of-inertia vector for a "chair" shape with mass=1, scale=1 + * (initialized by makeChair() + */ + public static Vector3f chairInverseInertia = null; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private CompoundTestShapes() { + } + // ************************************************************************* + // new methods exposed + + /** + * Generate a barbell shape with 2 cylindrical plates. + * + * @param barLength the total length of the bar (X axis, in unscaled shape + * units, >0) + * @param barRadius the radius of the bar (in unscaled shape units, >0) + * @param plateRadius the radius of each plate (in unscaled shape units, + * >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeBarbell( + float barLength, float barRadius, float plateRadius) { + Validate.positive(barLength, "bar length"); + Validate.positive(barRadius, "bar radius"); + Validate.positive(plateRadius, "plate radius"); + + float plateOffset = 0.42f * barLength; + CollisionShape bar = new CylinderCollisionShape( + barRadius, barLength, PhysicsSpace.AXIS_X); + + float plateThickness = 0.08f * barLength; + CollisionShape plate = new CylinderCollisionShape( + plateRadius, plateThickness, PhysicsSpace.AXIS_X); + + CompoundCollisionShape result = new CompoundCollisionShape(3); + result.addChildShape(bar); + result.addChildShape(plate, -plateOffset, 0f, 0f); + result.addChildShape(plate, plateOffset, 0f, 0f); + + return result; + } + + /** + * Approximate a hemispherical shell (or bowl), open on the +Z side, using + * 3-sphere shapes. + * + * @param innerRadius (in unscaled shape units, >thickness) + * @param thickness (in unscaled shape units, >0, <innerRadius) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeBowl( + float innerRadius, float thickness) { + Validate.inRange( + innerRadius, "inner radius", thickness, Float.MAX_VALUE); + Validate.inRange(thickness, "thickness", 0f, innerRadius); + + float halfThickness = thickness / 2f; + float midRadius = innerRadius + halfThickness; + int numRefineSteps = 2; + Octasphere mesh = new Octasphere(numRefineSteps, midRadius); + int numTriangles = mesh.getTriangleCount(); + + List radii = new ArrayList<>(3); + radii.add(thickness); + radii.add(thickness); + radii.add(thickness); + List centers = new ArrayList<>(3); + Vector3f v1 = new Vector3f(); + Vector3f v2 = new Vector3f(); + Vector3f v3 = new Vector3f(); + centers.add(v1); + centers.add(v2); + centers.add(v3); + Vector3f centroid = new Vector3f(); + float maxZ = 1e-4f; + CompoundCollisionShape result + = new CompoundCollisionShape(numTriangles / 2); + + for (int triangleI = 0; triangleI < numTriangles; ++triangleI) { + mesh.getTriangle(triangleI, v1, v2, v3); + if (v1.z < maxZ && v2.z < maxZ && v3.z < maxZ) { + centroid.zero(); + centroid.addLocal(v1).addLocal(v2).addLocal(v3); + centroid.divideLocal(3f); + + v1.subtractLocal(centroid); + v2.subtractLocal(centroid); + v3.subtractLocal(centroid); + + MultiSphere triSphere = new MultiSphere(centers, radii); + result.addChildShape(triSphere, centroid); + } + } + + int numChildren = result.countChildren(); + FloatBuffer masses = BufferUtils.createFloatBuffer(numChildren); + for (int childIndex = 0; childIndex < numChildren; ++childIndex) { + masses.put(1f / numChildren); + } + Vector3f inertia = new Vector3f(); + Transform transform + = result.principalAxes(masses, null, inertia); + bowlInverseInertia = Vector3f.UNIT_XYZ.divide(inertia); + result.correctAxes(transform); + + return result; + } + + /** + * Generate a chair with 4 cylindrical legs (asymmetrical). Must override + * the moments of inertia if used in a dynamic body. + * + * @param backLength the length of the back (Y axis, in unscaled shape + * units, >0) + * @param legLength the length of each leg (Y axis, in unscaled shape units, + * >0) + * @param legOffset the offset of each leg from the center of the seat (in + * unscaled shape units, >legRadius) + * @param legRadius the radius of each leg (in unscaled shape units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeChair(float backLength, + float legLength, float legOffset, float legRadius) { + Validate.positive(backLength, "back length"); + Validate.positive(legLength, "leg length"); + Validate.inRange(legOffset, "leg offset", legRadius, Float.MAX_VALUE); + Validate.positive(legRadius, "leg radius"); + + float seatHalf = legOffset + legRadius; + Vector3f halfExtents = new Vector3f(seatHalf, 0.2f, seatHalf); + RectangularSolid solid = new RectangularSolid(halfExtents); + CollisionShape seat = new MultiSphere(solid); + + CollisionShape frontLeg = new CylinderCollisionShape( + legRadius, legLength, PhysicsSpace.AXIS_Y); + + float rearLength = legLength + backLength; + CollisionShape rearLeg = new CylinderCollisionShape( + legRadius, rearLength, PhysicsSpace.AXIS_Y); + + float rearHalf = rearLength / 2f; + float legHalf = legLength / 2f; + float backHalf = backLength / 2f; + halfExtents.set(legOffset, backHalf, legRadius); + solid = new RectangularSolid(halfExtents); + CollisionShape back = new MultiSphere(solid); + + CompoundCollisionShape result = new CompoundCollisionShape(6); + result.addChildShape(seat); + result.addChildShape(frontLeg, legOffset, -legHalf, legOffset); + result.addChildShape(frontLeg, -legOffset, -legHalf, legOffset); + float yOffset = rearHalf - legLength; + result.addChildShape(rearLeg, legOffset, yOffset, -legOffset); + result.addChildShape(rearLeg, -legOffset, yOffset, -legOffset); + result.addChildShape(back, 0f, backHalf, -legOffset); + + float[] volumes = MyShape.listVolumes(result); + float sum = 0f; + for (float volume : volumes) { + sum += volume; + } + FloatBuffer masses = BufferUtils.createFloatBuffer(volumes.length); + for (float volume : volumes) { + masses.put(volume / sum); + } + Vector3f inertia = new Vector3f(); + Transform transform = result.principalAxes(masses, null, inertia); + chairInverseInertia = Vector3f.UNIT_XYZ.divide(inertia); + result.correctAxes(transform); + + return result; + } + + /** + * Generate an inverted hollow pyramid with its nadir on the -Y axis. Not + * intended for use in a dynamic body. TODO use MeshCollisionShape + * + * @param numSides the number of sides (≥3) + * @param rimRadius (in unscaled shape units, >0) + * @param depth rimY minus nadirY (in unscaled shape units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeCorner( + int numSides, float rimRadius, float depth) { + Validate.inRange(numSides, "number of sides", 3, Integer.MAX_VALUE); + Validate.positive(rimRadius, "rim radius"); + Validate.positive(depth, "depth"); + + float stepAngle = FastMath.TWO_PI / numSides; // in radians + Matrix3f rotationMatrix = new Matrix3f(); + rotationMatrix.fromAngleAxis(stepAngle, Vector3f.UNIT_Y); + + float rimY = 0.3f * depth; + Vector3f vertex1 = new Vector3f(rimRadius, rimY, 0f); + Vector3f vertex2 = rotationMatrix.mult(vertex1, null); + Vector3f nadir = new Vector3f(0f, rimY - depth, 0f); + SimplexCollisionShape triangle + = new SimplexCollisionShape(vertex1, vertex2, nadir); + + CompoundCollisionShape result = new CompoundCollisionShape(numSides); + result.addChildShape(triangle); + result.addChildShape(triangle, Vector3f.ZERO, rotationMatrix); + for (int i = 2; i < numSides; ++i) { + float angle = stepAngle * i; + rotationMatrix.fromAngleAxis(angle, Vector3f.UNIT_Y); + result.addChildShape(triangle, Vector3f.ZERO, rotationMatrix); + } + + return result; + } + + /** + * Generate a rectangular frame, open on the Z axis. + * + * @param ihHeight half of the internal height (Y direction, in unscaled + * shape units, >0) + * @param ihWidth half of the internal width (X direction, in unscaled shape + * units, >0) + * @param halfDepth half of the (external) depth (Z direction, in unscaled + * shape units, >0) + * @param halfThickness half the thickness (in unscaled shape units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeFrame(float ihHeight, + float ihWidth, float halfDepth, float halfThickness) { + Validate.positive(ihHeight, "half height"); + Validate.positive(ihWidth, "half width"); + Validate.positive(halfDepth, "half depth"); + Validate.positive(halfThickness, "half thickness"); + + float mhHeight = ihHeight + halfThickness; + float mhWidth = ihWidth + halfThickness; + + CollisionShape horizontal + = new BoxCollisionShape(mhWidth, halfThickness, halfDepth); + CollisionShape vertical + = new BoxCollisionShape(halfThickness, mhHeight, halfDepth); + + CompoundCollisionShape result = new CompoundCollisionShape(4); + result.addChildShape(horizontal, halfThickness, -mhHeight, 0f); + result.addChildShape(horizontal, -halfThickness, mhHeight, 0f); + result.addChildShape(vertical, mhWidth, halfThickness, 0f); + result.addChildShape(vertical, -mhWidth, -halfThickness, 0f); + + return result; + } + + /** + * Generate an I-beam. + * + * @param length (Z axis, in unscaled shape units, >0) + * @param flangeWidth (X axis, in unscaled shape units, ≥thickness) + * @param beamHeight (Y axis, in unscaled shape units, ≥2*thickness) + * @param thickness (in unscaled shape units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeIBeam(float length, + float flangeWidth, float beamHeight, float thickness) { + Validate.positive(length, "length"); + Validate.positive(thickness, "thickness"); + Validate.inRange( + flangeWidth, "flange width", thickness, Float.MAX_VALUE); + Validate.inRange( + beamHeight, "beam height", 2f * thickness, Float.MAX_VALUE); + + float halfLength = length / 2f; + float halfThickness = thickness / 2f; + float webHalfHeight = beamHeight / 2f - thickness; + CollisionShape web = new BoxCollisionShape( + halfThickness, webHalfHeight, halfLength); + CollisionShape flange = new BoxCollisionShape( + flangeWidth / 2f, halfThickness, halfLength); + + CompoundCollisionShape result = new CompoundCollisionShape(3); + result.addChildShape(web); + float flangeY = webHalfHeight + halfThickness; + result.addChildShape(flange, 0f, flangeY, 0f); + result.addChildShape(flange, 0f, -flangeY, 0f); + + return result; + } + + /** + * Generate a knucklebone with 4 spherical balls. (No balls on the Z axis.) + * + * @param stemLength the length of each stem (in unscaled shape units, + * >0) + * @param stemRadius the radius of each stem (in unscaled shape units, + * >0) + * @param ballRadius the radius of each ball (in unscaled shape units, + * >stemRadius) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeKnucklebone( + float stemLength, float stemRadius, float ballRadius) { + Validate.positive(stemLength, "stem length"); + Validate.positive(stemRadius, "stem radius"); + Validate.positive(ballRadius, "ball radius"); + + CollisionShape xStem = new CapsuleCollisionShape( + stemRadius, stemLength, PhysicsSpace.AXIS_X); + CollisionShape yStem = new CapsuleCollisionShape( + stemRadius, stemLength, PhysicsSpace.AXIS_Y); + CollisionShape zStem = new CapsuleCollisionShape( + stemRadius, stemLength, PhysicsSpace.AXIS_Z); + + CollisionShape ball = new SphereCollisionShape(ballRadius); + + CompoundCollisionShape result = new CompoundCollisionShape(7); + result.addChildShape(xStem); + result.addChildShape(yStem); + result.addChildShape(zStem); + + float stemHalf = stemLength / 2f; + result.addChildShape(ball, stemHalf, 0f, 0f); + result.addChildShape(ball, -stemHalf, 0f, 0f); + result.addChildShape(ball, 0f, stemHalf, 0f); + result.addChildShape(ball, 0f, -stemHalf, 0f); + + return result; + } + + /** + * Generate a ladder with 5 cylindrical rungs. + * + * @param rungLength the total length of each rung (X direction, in unscaled + * shape units, >0) + * @param rungSpacing the spacing between rungs (Y direction, in unscaled + * shape units, >2*rungRadius) + * @param rungRadius the radius of each rung (in unscaled shape units, + * >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeLadder( + float rungLength, float rungSpacing, float rungRadius) { + Validate.positive(rungLength, "rung length"); + Validate.inRange( + rungSpacing, "rung spacing", 2f * rungRadius, Float.MAX_VALUE); + Validate.positive(rungRadius, "rung radius"); + + CollisionShape rung = new CylinderCollisionShape( + rungRadius, rungLength, PhysicsSpace.AXIS_X); + + float railLength = 6f * rungSpacing; + float railHalf = railLength / 2f; + CollisionShape rail + = new BoxCollisionShape(rungRadius, railHalf, rungRadius); + + CompoundCollisionShape result = new CompoundCollisionShape(7); + result.addChildShape(rung, 0f, 2f * rungSpacing, 0f); + result.addChildShape(rung, 0f, rungSpacing, 0f); + result.addChildShape(rung); + result.addChildShape(rung, 0f, -rungSpacing, 0f); + result.addChildShape(rung, 0f, -2f * rungSpacing, 0f); + + float rungHalf = rungLength / 2f; + result.addChildShape(rail, rungHalf, 0f, 0f); + result.addChildShape(rail, -rungHalf, 0f, 0f); + + return result; + } + + /** + * Generate a lidless rectangular box with its opening on the +Z side. + * + * @param iHeight the internal height (Y direction, in unscaled shape units, + * >0) + * @param iWidth the internal width (X direction, in unscaled shape units, + * >0) + * @param iDepth the internal depth (Z direction, in unscaled shape units, + * >0) + * @param wallThickness (in unscaled shape units, >0) + * + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeLidlessBox( + float iHeight, float iWidth, float iDepth, float wallThickness) { + Validate.positive(iHeight, "internal height"); + Validate.positive(iWidth, "internal width"); + Validate.positive(iDepth, "internal depth"); + Validate.positive(wallThickness, "wall thickness"); + + float ihHeight = iHeight / 2f; + float ihWidth = iWidth / 2f; + float ihDepth = iDepth / 2f; + float halfThickness = wallThickness / 2f; + + float fhDepth = ihDepth + halfThickness; + CompoundCollisionShape result + = makeFrame(ihHeight, ihWidth, fhDepth, halfThickness); + + BoxCollisionShape bottom + = new BoxCollisionShape(ihWidth, ihHeight, halfThickness); + result.addChildShape(bottom, 0f, 0f, -ihDepth); + + return result; + } + + /** + * Generate a rectangular link for a chain. + * + * @param ihHeight half of the internal height (Y direction, in unscaled + * shape units, >0) + * @param ihWidth half of the internal width (X direction, in unscaled shape + * units, >0) + * @param radius half the thickness (in unscaled shape units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeLink( + float ihHeight, float ihWidth, float radius) { + Validate.positive(ihHeight, "half height"); + Validate.positive(ihWidth, "half width"); + Validate.positive(radius, "radius"); + + float mhHeight = ihHeight + radius; + float mhWidth = ihWidth + radius; + + CollisionShape horizontal = new CapsuleCollisionShape( + radius, 2f * mhWidth, PhysicsSpace.AXIS_X); + CollisionShape vertical = new CapsuleCollisionShape( + radius, 2f * mhHeight, PhysicsSpace.AXIS_Y); + + CompoundCollisionShape result = new CompoundCollisionShape(4); + result.addChildShape(horizontal, 0f, -mhHeight, 0f); + result.addChildShape(horizontal, 0f, mhHeight, 0f); + result.addChildShape(vertical, mhWidth, 0f, 0f); + result.addChildShape(vertical, -mhWidth, 0f, 0f); + + return result; + } + + /** + * Generate a mad mallet by compounding 2 cylinders. + * + * @param handleR the radius of the handle (in unscaled shape units, >0) + * @param headR the radius of the head (in unscaled shape units, >0) + * @param handleHalfLength half the length of the handle (in unscaled shape + * units, >0) + * @param headHalfLength half the length of the head (in unscaled shape + * units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeMadMallet(float handleR, + float headR, float handleHalfLength, float headHalfLength) { + Validate.positive(handleR, "handle radius"); + Validate.positive(headR, "head radius"); + Validate.positive(handleHalfLength, "handle half length"); + Validate.positive(headHalfLength, "head half length"); + + Vector3f hes = new Vector3f(headR, headR, headHalfLength); + CollisionShape head = new CylinderCollisionShape(hes); + + hes.set(handleR, handleR, handleHalfLength); + CollisionShape handle = new CylinderCollisionShape(hes); + + CompoundCollisionShape result = new CompoundCollisionShape(2); + + result.addChildShape(handle, 0f, 0f, handleHalfLength); + + Vector3f offset = new Vector3f(0f, 0f, 2f * handleHalfLength); + Matrix3f rotation = new Matrix3f(); + rotation.fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X); + result.addChildShape(head, offset, rotation); + + return result; + } + + /** + * Approximate an arc of a straight, square-ended pipe (or of a flat ring), + * open on the Z axis, using hulls. + * + * @param innerR the inner radius of an X-Y cross section (in unscaled shape + * units, >0) + * @param thickness the thickness of the pipe (in unscaled shape units, + * >0) + * @param zLength the length of the pipe (Z direction, in unscaled shape + * units, >0) + * @param arc the arc amount (in radians, >0, ≤2pi) + * @param numChildren the number of child shapes to create (≥3) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makePipe(float innerR, float thickness, + float zLength, float arc, int numChildren) { + Validate.positive(innerR, "inner radius"); + Validate.positive(thickness, "thickness"); + Validate.positive(zLength, "length"); + Validate.inRange(arc, "arc", 0f, FastMath.TWO_PI); + Validate.inRange( + numChildren, "number of children", 3, Integer.MAX_VALUE); + + float halfLength = zLength / 2f; + float outerR = innerR + thickness; + float segmentAngle = arc / numChildren; // in radians + + float xOff; + float yOff; // TODO more accurate centering + if (arc < 2) { + float cos = FastMath.cos(segmentAngle); + float sin = FastMath.sin(segmentAngle); + xOff = (1 + cos * outerR) / 2f; + yOff = sin * innerR / 2f; + + } else if (arc < 4) { + xOff = 0f; + yOff = outerR / 2f; + + } else { + xOff = 0f; + yOff = 0f; + } + + CompoundCollisionShape result = new CompoundCollisionShape(numChildren); + for (int segmentI = 0; segmentI < numChildren; ++segmentI) { + float theta1 = segmentI * segmentAngle; + float theta2 = (segmentI + 1) * segmentAngle; + float cos1 = FastMath.cos(theta1); + float cos2 = FastMath.cos(theta2); + float sin1 = FastMath.sin(theta1); + float sin2 = FastMath.sin(theta2); + + FloatBuffer buffer = BufferUtils.createFloatBuffer( + innerR * cos1 - xOff, innerR * sin1 - yOff, halfLength, + innerR * cos2 - xOff, innerR * sin2 - yOff, halfLength, + outerR * cos1 - xOff, outerR * sin1 - yOff, halfLength, + outerR * cos2 - xOff, outerR * sin2 - yOff, halfLength, + innerR * cos1 - xOff, innerR * sin1 - yOff, -halfLength, + innerR * cos2 - xOff, innerR * sin2 - yOff, -halfLength, + outerR * cos1 - xOff, outerR * sin1 - yOff, -halfLength, + outerR * cos2 - xOff, outerR * sin2 - yOff, -halfLength + ); + HullCollisionShape child = new HullCollisionShape(buffer); + result.addChildShape(child); + } + + return result; + } + + /** + * Generate a wire sieve, backed by a horizontal plane. Not intended for use + * in a dynamic body. + * + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeSieve() { + float wireSpacing = 2f; + float yHeight = 7f; + int numXWires = 16; + int numZWires = 16; + + float xHalfExtent = (numZWires - 1) * wireSpacing / 2f; + float zHalfExtent = (numXWires - 1) * wireSpacing / 2f; + CompoundCollisionShape result + = new CompoundCollisionShape(numXWires + numZWires + 1); + + // Add numXWires wires parallel to the X axis. + Vector3f p0 = new Vector3f(-xHalfExtent, 0f, 0f); + Vector3f p1 = new Vector3f(xHalfExtent, 0f, 0f); + SimplexCollisionShape xWire = new SimplexCollisionShape(p0, p1); + + for (int zIndex = 0; zIndex < numXWires; ++zIndex) { + float z = -zHalfExtent + wireSpacing * zIndex; + result.addChildShape(xWire, 0f, yHeight, z); + } + + // Add numZWires wires parallel to the Z axis. + p0.set(0f, 0f, -zHalfExtent); + p1.set(0f, 0f, zHalfExtent); + SimplexCollisionShape zWire = new SimplexCollisionShape(p0, p1); + + for (int xIndex = 0; xIndex < numZWires; ++xIndex) { + float x = -xHalfExtent + wireSpacing * xIndex; + result.addChildShape(zWire, x, yHeight, 0f); + } + + // Add a plane to catch any drops that pass through the sieve. + Plane plane = new Plane(Vector3f.UNIT_Y, 0f); + PlaneCollisionShape pcs = new PlaneCollisionShape(plane); + result.addChildShape(pcs); + + return result; + } + + /** + * Generate a 3-ball snowman with its head on the +Y axis. + * + * @param baseRadius (in unscaled shape units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeSnowman(float baseRadius) { + Validate.positive(baseRadius, "base radius"); + + float verticalAngle = 2f; // radians + HullCollisionShape base + = MinieTestShapes.makeDome(baseRadius, verticalAngle); + + float torsoRadius = 0.8f * baseRadius; + SphereCollisionShape torso = new SphereCollisionShape(torsoRadius); + + float headRadius = 0.6f * baseRadius; + SphereCollisionShape head = new SphereCollisionShape(headRadius); + + CompoundCollisionShape result = new CompoundCollisionShape(3); + result.addChildShape(base, 0f, -0.5f * baseRadius, 0f); + result.addChildShape(torso, 0f, 0.7f * torsoRadius, 0f); + result.addChildShape(head, 0f, 2.1f * torsoRadius, 0f); + + return result; + } + + /** + * Generate a star shape. + * + * @param numPoints the number of points (≥2) + * @param outerRadius the outer radius (in unscaled shape units, >0) + * @param centerY the half thickness at the center (>0) + * @param radiusRatio the inner radius divided by the outer radius (>0, + * <1) + * @param trianglesPerSlice the number of mesh triangles per slice (4 or 6) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeStar( + int numPoints, float outerRadius, float centerY, float radiusRatio, + int trianglesPerSlice) { + Validate.inRange(numPoints, "number of points", 2, Integer.MAX_VALUE); + Validate.positive(outerRadius, "outer radius"); + Validate.positive(centerY, "center Y"); + Validate.fraction(radiusRatio, "radius ratio"); + Validate.inRange(trianglesPerSlice, "triangles per slice", 4, 6); + + float innerRadius = radiusRatio * outerRadius; + float sliceAngle = FastMath.TWO_PI / numPoints; // in radians + boolean normals = false; + StarSlice sliceMesh = new StarSlice(sliceAngle, innerRadius, + outerRadius, 2f * centerY, normals, trianglesPerSlice); + CollisionShape sliceShape = new HullCollisionShape(sliceMesh); + + CompoundCollisionShape result = new CompoundCollisionShape(numPoints); + Matrix3f rotate = new Matrix3f(); + for (int pointIndex = 0; pointIndex < numPoints; ++pointIndex) { + rotate.fromAngleAxis(sliceAngle * pointIndex, Vector3f.UNIT_Y); + result.addChildShape(sliceShape, Vector3f.ZERO, rotate); + } + + return result; + } + + /** + * Generate a round pedestal table with its top on the +Y axis. + * + * @param topRadius the radius of the top (in unscaled shape units, + * >pedestalRadius) + * @param pedestalRadius the radius of the pedestal (in unscaled shape + * units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeTable( + float topRadius, float pedestalRadius) { + Validate.inRange( + topRadius, "top radius", pedestalRadius, Float.MAX_VALUE); + Validate.positive(pedestalRadius, "pedestal radius"); + + float thickness = 0.4f; + float footHalfLength = 0.6f * (topRadius + pedestalRadius); + CollisionShape foot + = new BoxCollisionShape(thickness / 2f, 0.25f, footHalfLength); + + float pedestalHeight = 4f; + CollisionShape pedestal = new CylinderCollisionShape( + pedestalRadius, pedestalHeight, PhysicsSpace.AXIS_Y); + + CollisionShape top = new CylinderCollisionShape( + topRadius, thickness, PhysicsSpace.AXIS_Y); + + CompoundCollisionShape result = new CompoundCollisionShape(4); + result.addChildShape(pedestal, 0f, -1f, 0f); + result.addChildShape(top, 0f, 1.2f, 0f); + + Vector3f footOffset = new Vector3f(0f, -2.75f, 0f); + result.addChildShape(foot, footOffset); + + Matrix3f rotY90 = new Matrix3f(); + rotY90.fromAngleAxis(FastMath.HALF_PI, new Vector3f(0f, 1f, 0f)); + result.addChildShape(foot, footOffset, rotY90); + + return result; + } + + /** + * Generate a thumb tack (drawing pin) with a round, flat head on the +Y + * axis. + * + * @param headRadius the radius of the head (in unscaled units, + * >spikeRadius) + * @param spikeRadius the radius of the spike (in unscaled units, >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeThumbTack( + float headRadius, float spikeRadius) { + Validate.inRange( + headRadius, "head radius", spikeRadius, Float.MAX_VALUE); + Validate.positive(spikeRadius, "spike radius"); + + float headThickness = 0.4f; + CollisionShape head = new CylinderCollisionShape( + headRadius, headThickness, PhysicsSpace.AXIS_Y); + + float spikeHeight = 3f; + CollisionShape spike = new ConeCollisionShape( + spikeRadius, spikeHeight, PhysicsSpace.AXIS_Y); + + CompoundCollisionShape result = new CompoundCollisionShape(2); + result.addChildShape(head, 0f, -0.5f, 0f); + result.addChildShape(spike, 0f, 1f, 0f); + + return result; + } + + /** + * Generate a top with a cylindrical body, its handle on the +Y axis. + * + * @param bodyRadius the radius of the body (in unscaled shape units, + * >handleRadius) + * @param handleRadius the radius of the handle (in unscaled shape units, + * >0) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeTop( + float bodyRadius, float handleRadius) { + Validate.inRange( + bodyRadius, "body radius", handleRadius, Float.MAX_VALUE); + Validate.positive(handleRadius, "handle radius"); + + float bodyHeight = 0.6f; + CollisionShape body = new CylinderCollisionShape( + bodyRadius, bodyHeight, PhysicsSpace.AXIS_Y); + + float coneHeight = 1.5f; + CollisionShape cone = new ConeCollisionShape( + bodyRadius - 0.06f, coneHeight, PhysicsSpace.AXIS_Y); + + float handleHeight = 1.5f; + CollisionShape handle = new CapsuleCollisionShape( + handleRadius, handleHeight, PhysicsSpace.AXIS_Y); + + CompoundCollisionShape result = new CompoundCollisionShape(3); + result.addChildShape(body); + float yOffset = (coneHeight + bodyHeight) / 2f; + result.addChildShape(cone, 0f, yOffset, 0f); + yOffset = -0.5f * (handleHeight + bodyHeight); + result.addChildShape(handle, 0f, yOffset, 0f); + + return result; + } + + /** + * Approximate a torus (or donut), open on the Z axis, using capsules + * arranged in a circle. + * + * @param majorRadius (in unscaled shape units, >minorRadius) + * @param minorRadius (in unscaled shape units, >0, <majorRadius) + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape + makeTorus(float majorRadius, float minorRadius) { + Validate.inRange( + majorRadius, "major radius", minorRadius, Float.MAX_VALUE); + Validate.inRange( + minorRadius, "minor radius", Float.MIN_VALUE, majorRadius); + + int numCapsules = 20; + float angle = FastMath.TWO_PI / numCapsules; + float length = majorRadius * angle; + CollisionShape capsule = new CapsuleCollisionShape( + minorRadius, length, PhysicsSpace.AXIS_X); + + CompoundCollisionShape result = new CompoundCollisionShape(numCapsules); + Vector3f offset = new Vector3f(); + Matrix3f rotation = new Matrix3f(); + + for (int childI = 0; childI < numCapsules; ++childI) { + float theta = angle * childI; + offset.x = majorRadius * FastMath.sin(theta); + offset.y = majorRadius * FastMath.cos(theta); + rotation.fromAngleNormalAxis(-theta, Vector3f.UNIT_Z); + + result.addChildShape(capsule, offset, rotation); + } + + return result; + } + + /** + * Generate a square tray with a central deflector. Not intended for use in + * a dynamic body. + * + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape makeTray() { + float iHeight = 24f; + float iWidth = 24f; + float iDepth = 3f; + float wallThickness = 3f; + CompoundCollisionShape result + = makeLidlessBox(iHeight, iWidth, iDepth, wallThickness); + + Matrix3f rotMatrix = new Matrix3f(); + rotMatrix.fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_X); + result.rotate(rotMatrix); + + float baseY = -1.5f; + Vector3f depress = new Vector3f(0f, baseY, 0f); + result.translate(depress); + + // Place a tetrahedral deflector in the center. + float size = 3f; + Vector3f p1 = new Vector3f(0f, size, 0f); + Vector3f p2 = new Vector3f(-size, baseY, size); + Vector3f p3 = new Vector3f(-size, baseY, -size); + Vector3f p4 = new Vector3f(size * MyMath.root2, baseY, 0f); + CollisionShape child = new SimplexCollisionShape(p1, p2, p3, p4); + result.addChildShape(child); + + return result; + } + + /** + * Generate a trident. + * + * @param shaftLength (Y direction, in unscaled shape units, >0) + * @param shaftRadius (in unscaled shape units, >0) + * + * @return a new compound shape (not null) + */ + public static CompoundCollisionShape + makeTrident(float shaftLength, float shaftRadius) { + Validate.positive(shaftLength, "shaft length"); + Validate.positive(shaftRadius, "shaft radius"); + + // Create a cylinder for the shaft. + CollisionShape shaft = new CylinderCollisionShape( + shaftRadius, shaftLength, PhysicsSpace.AXIS_Y); + float shaftOffset = 0.2f * shaftLength; + + // Create a box for the crosspiece. + float halfCross = 5f * shaftRadius; + float halfThickness = 0.75f * shaftRadius; + float margin = CollisionShape.getDefaultMargin(); + CollisionShape crosspiece = new BoxCollisionShape(halfCross + margin, + halfThickness + margin, halfThickness + margin); + + // Create pyramidal hulls for each of the 3 prongs. + float baseX = halfCross - halfThickness; + float pointX = halfCross + 2f * halfThickness; + float crossOffset + = shaftLength / 2f - shaftOffset + halfThickness + margin; + float baseY = crossOffset + halfThickness; + float sideY = baseY + 3f * halfCross; + float mainY = baseY + 4f * halfCross; + float[] array1 = { + halfCross, baseY, +halfThickness, + halfCross, baseY, -halfThickness, + baseX, baseY, +halfThickness, + baseX, baseY, -halfThickness, + pointX, sideY, 0f + }; + CollisionShape rightProng = new HullCollisionShape(array1); + + float[] array2 = { + -halfThickness, baseY, +halfThickness, + -halfThickness, baseY, -halfThickness, + +halfThickness, baseY, +halfThickness, + +halfThickness, baseY, -halfThickness, + 0f, mainY, 0f + }; + CollisionShape middleProng = new HullCollisionShape(array2); + + float[] array3 = { + -halfCross, baseY, +halfThickness, + -halfCross, baseY, -halfThickness, + -baseX, baseY, +halfThickness, + -baseX, baseY, -halfThickness, + -pointX, sideY, 0f + }; + CollisionShape leftProng = new HullCollisionShape(array3); + + CompoundCollisionShape result = new CompoundCollisionShape(5); + result.addChildShape(shaft, 0f, -shaftOffset, 0f); + result.addChildShape(crosspiece, 0f, crossOffset, 0f); + result.addChildShape(rightProng); + result.addChildShape(middleProng); + result.addChildShape(leftProng); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/MinieTestShapes.java b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/MinieTestShapes.java index b0372841f..5c8cdbbfe 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/MinieTestShapes.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/MinieTestShapes.java @@ -1,361 +1,361 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shape; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.Convex2dShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.collision.shapes.SimplexCollisionShape; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; -import jme3utilities.Validate; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.mesh.DomeMesh; -import jme3utilities.mesh.RoundedRectangle; -import jme3utilities.minie.test.terrain.MinieTestTerrains; - -/** - * Utility class to generate collision shapes for use in MinieExamples. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MinieTestShapes { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(MinieTestShapes.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MinieTestShapes() { - } - // ************************************************************************* - // new methods exposed - - /** - * Add some shapes to the specified library. - * - * @param namedShapes where to add shapes (not null, modified) - */ - public static void addShapes(Map namedShapes) { - { - float barLength = 4.8f; // TODO randomize - float barRadius = 0.2f; - float plateRadius = 1f; - CollisionShape barbell = CompoundTestShapes.makeBarbell( - barLength, barRadius, plateRadius); - namedShapes.put("barbell", barbell); - } - - CollisionShape bedOfNails = makeBedOfNails(); - namedShapes.put("bedOfNails", bedOfNails); - - { - float innerRadius = 3f; // TODO randomize - float thickness = 0.3f; - CollisionShape bowl - = CompoundTestShapes.makeBowl(innerRadius, thickness); - namedShapes.put("bowl", bowl); - } - - { - float backLength = 3f; // TODO randomize - float legLength = 2f; - float legOffset = 1f; - float legRadius = 0.2f; - CollisionShape chair = CompoundTestShapes.makeChair( - backLength, legLength, legOffset, legRadius); - namedShapes.put("chair", chair); - } - - { - int numSides = 4; - float radius = 20f; - float depth = 12f; - CollisionShape corner - = CompoundTestShapes.makeCorner(numSides, radius, depth); - namedShapes.put("corner", corner); - } - - CollisionShape dimples = makeDimples(); - namedShapes.put("dimples", dimples); - - { - float stemLength = 2.5f; // TODO randomize - float stemRadius = 0.25f; - float ballRadius = 0.4f; - CollisionShape knucklebone = CompoundTestShapes.makeKnucklebone( - stemLength, stemRadius, ballRadius); - namedShapes.put("knucklebone", knucklebone); - } - - { - float rungLength = 2f; // TODO randomize - float rungSpacing = 2f; - float rungRadius = 0.2f; - CollisionShape ladder = CompoundTestShapes.makeLadder( - rungLength, rungSpacing, rungRadius); - namedShapes.put("ladder", ladder); - } - - { - float ihHeight = 1f; - float ihWidth = 0.5f; - float radius = 0.25f; - CollisionShape link - = CompoundTestShapes.makeLink(ihHeight, ihWidth, radius); - namedShapes.put("link", link); - } - - CollisionShape roundedRectangle = makeRoundedRectangle(); - namedShapes.put("roundedRectangle", roundedRectangle); - - CollisionShape sieve = CompoundTestShapes.makeSieve(); - namedShapes.put("sieve", sieve); - - CollisionShape smooth = makeSmoothHeightfield(); - namedShapes.put("smooth", smooth); - - { - float topRadius = 3f; // TODO randomize - float pedestalRadius = 0.3f; - CollisionShape table - = CompoundTestShapes.makeTable(topRadius, pedestalRadius); - namedShapes.put("table", table); - } - - { - float headRadius = 2f; // TODO randomize - float spikeRadius = 0.2f; - CollisionShape thumbTack - = CompoundTestShapes.makeThumbTack(headRadius, spikeRadius); - namedShapes.put("thumbTack", thumbTack); - } - - { - float bodyRadius = 1.5f; // TODO randomize - float handleRadius = 0.3f; - CollisionShape top - = CompoundTestShapes.makeTop(bodyRadius, handleRadius); - namedShapes.put("top", top); - } - - CollisionShape tray = CompoundTestShapes.makeTray(); - namedShapes.put("tray", tray); - - CollisionShape triangle = makeTriangle(); - namedShapes.put("triangle", triangle); - } - - /** - * Generate a bed-of-nails heightfield. Not intended for use in a dynamic - * body. - * - * @return a new heightfield shape (not null) - */ - public static HeightfieldCollisionShape makeBedOfNails() { - int n = 64; - float[] array = MinieTestTerrains.bedOfNailsArray(n); - - Vector3f scale = new Vector3f(40f / n, 4f, 40f / n); - int upAxis = PhysicsSpace.AXIS_Y; - boolean flipQuadEdges = false; - boolean flipTriangleWinding = false; - boolean useDiamond = true; - boolean useZigzag = false; - HeightfieldCollisionShape result = new HeightfieldCollisionShape( - n, n, array, scale, upAxis, flipQuadEdges, - flipTriangleWinding, useDiamond, useZigzag); - result.setContactFilterEnabled(false); // reduce risk of fallthru - - return result; - } - - /** - * Generate a dimpled heightfield. Not intended for use in a dynamic body. - * - * @return a new heightfield shape (not null) - */ - public static HeightfieldCollisionShape makeDimples() { - int n = 128; - float[] array = MinieTestTerrains.dimplesArray(n); - - Vector3f scale = new Vector3f(40f / n, 1f, 40f / n); - HeightfieldCollisionShape result - = new HeightfieldCollisionShape(array, scale); - result.setContactFilterEnabled(false); // reduce risk of fallthru - - return result; - } - - /** - * Generate a spherical dome or plano-convex lens. - * - * @param radius (in unscaled shape units, >0) - * @param verticalAngle the central angle from the top to the rim (in - * radians, <Pi, >0, Pi/2 → hemisphere) - * @return a new hull shape - */ - public static HullCollisionShape makeDome( - float radius, float verticalAngle) { - Validate.positive(radius, "radius"); - Validate.inRange(verticalAngle, "vertical angle", 0f, FastMath.PI); - - int rimSamples = 20; - int quadrantSamples = 10; - DomeMesh mesh = new DomeMesh(rimSamples, quadrantSamples); - mesh.setVerticalAngle(verticalAngle); - FloatBuffer buffer = mesh.getFloatBuffer(VertexBuffer.Type.Position); - - // Scale mesh positions to the desired radius. - int start = 0; - int end = buffer.limit(); - Vector3f scale = new Vector3f(radius, radius, radius); - MyBuffer.scale(buffer, start, end, scale); - - // Use max-min to center the vertices. - Vector3f max = new Vector3f(); - Vector3f min = new Vector3f(); - MyBuffer.maxMin(buffer, start, end, max, min); - Vector3f offset = MyVector3f.midpoint(min, max, null).negateLocal(); - MyBuffer.translate(buffer, start, end, offset); - - HullCollisionShape result = new HullCollisionShape(buffer); - - return result; - } - - /** - * Generate a (gridiron) football using overlapping spheres arranged in a - * row. - * - * @param midRadius the radius of the Y-Z cross section at X=0 (in unscaled - * shape units, ≥0) - * @return a new MultiSphere shape (not null) - */ - public static MultiSphere makeFootball(float midRadius) { - float genRadius = 2f * midRadius; // curvature radius of the generatrix - float endRadius = 0.5f * midRadius; // controls pointiness of the ends - - int numSpheres = 9; - List centers = new ArrayList<>(numSpheres); - List radii = new ArrayList<>(numSpheres); - - float centerY = genRadius - midRadius; - float maxX = FastMath.sqrt(genRadius * genRadius - centerY * centerY); - float lastCenterX = maxX - endRadius; - - float xStep = (2f * lastCenterX) / (numSpheres - 1); - for (int sphereI = 0; sphereI < numSpheres; ++sphereI) { - float centerX = -lastCenterX + sphereI * xStep; - // centerX varies from -lastCenterX to +lastCenterX - centers.add(new Vector3f(centerX, 0f, 0f)); - - float radius = genRadius - MyMath.hypotenuse(centerX, centerY); - radii.add(radius); - } - - MultiSphere result = new MultiSphere(centers, radii); - - return result; - } - - /** - * Generate a large rounded rectangle. - * - * @return a new convex 2-D shape (not null) - */ - public static Convex2dShape makeRoundedRectangle() { - float halfExtent = 30f; - - float x2 = halfExtent; - float y2 = halfExtent / MyMath.phi; - float x1 = -x2; - float y1 = -y2; - float cornerRadius = 4f; - float zNorm = 1f; - Mesh mesh = new RoundedRectangle(x1, x2, y1, y2, cornerRadius, zNorm); - HullCollisionShape hull = new HullCollisionShape(mesh); - Convex2dShape result = new Convex2dShape(hull); - - return result; - } - - /** - * Generate a smooth 64x64 heightfield. Not intended for use in a dynamic - * body. - * - * @return a new heightfield shape (not null) - */ - public static HeightfieldCollisionShape makeSmoothHeightfield() { - int n = 64; - float[] array = MinieTestTerrains.quadraticArray(n); - - Vector3f scale = new Vector3f(40f / n, 12.5f, 40f / n); - HeightfieldCollisionShape result - = new HeightfieldCollisionShape(array, scale); - result.setContactFilterEnabled(false); // reduce risk of fallthru - - return result; - } - - /** - * Generate a large isosceles triangle. - * - * @return a new simplex shape (not null) - */ - public static SimplexCollisionShape makeTriangle() { - float radius = 50f; - - float x = radius * MyMath.rootHalf; - Vector3f p1 = new Vector3f(x, 0f, x); - Vector3f p2 = new Vector3f(x, 0f, -x); - Vector3f p3 = new Vector3f(-x, 0f, 0f); - SimplexCollisionShape result = new SimplexCollisionShape(p1, p2, p3); - - return result; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shape; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.Convex2dShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.collision.shapes.SimplexCollisionShape; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.mesh.DomeMesh; +import jme3utilities.mesh.RoundedRectangle; +import jme3utilities.minie.test.terrain.MinieTestTerrains; + +/** + * Utility class to generate collision shapes for use in MinieExamples. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MinieTestShapes { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(MinieTestShapes.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MinieTestShapes() { + } + // ************************************************************************* + // new methods exposed + + /** + * Add some shapes to the specified library. + * + * @param namedShapes where to add shapes (not null, modified) + */ + public static void addShapes(Map namedShapes) { + { + float barLength = 4.8f; // TODO randomize + float barRadius = 0.2f; + float plateRadius = 1f; + CollisionShape barbell = CompoundTestShapes.makeBarbell( + barLength, barRadius, plateRadius); + namedShapes.put("barbell", barbell); + } + + CollisionShape bedOfNails = makeBedOfNails(); + namedShapes.put("bedOfNails", bedOfNails); + + { + float innerRadius = 3f; // TODO randomize + float thickness = 0.3f; + CollisionShape bowl + = CompoundTestShapes.makeBowl(innerRadius, thickness); + namedShapes.put("bowl", bowl); + } + + { + float backLength = 3f; // TODO randomize + float legLength = 2f; + float legOffset = 1f; + float legRadius = 0.2f; + CollisionShape chair = CompoundTestShapes.makeChair( + backLength, legLength, legOffset, legRadius); + namedShapes.put("chair", chair); + } + + { + int numSides = 4; + float radius = 20f; + float depth = 12f; + CollisionShape corner + = CompoundTestShapes.makeCorner(numSides, radius, depth); + namedShapes.put("corner", corner); + } + + CollisionShape dimples = makeDimples(); + namedShapes.put("dimples", dimples); + + { + float stemLength = 2.5f; // TODO randomize + float stemRadius = 0.25f; + float ballRadius = 0.4f; + CollisionShape knucklebone = CompoundTestShapes.makeKnucklebone( + stemLength, stemRadius, ballRadius); + namedShapes.put("knucklebone", knucklebone); + } + + { + float rungLength = 2f; // TODO randomize + float rungSpacing = 2f; + float rungRadius = 0.2f; + CollisionShape ladder = CompoundTestShapes.makeLadder( + rungLength, rungSpacing, rungRadius); + namedShapes.put("ladder", ladder); + } + + { + float ihHeight = 1f; + float ihWidth = 0.5f; + float radius = 0.25f; + CollisionShape link + = CompoundTestShapes.makeLink(ihHeight, ihWidth, radius); + namedShapes.put("link", link); + } + + CollisionShape roundedRectangle = makeRoundedRectangle(); + namedShapes.put("roundedRectangle", roundedRectangle); + + CollisionShape sieve = CompoundTestShapes.makeSieve(); + namedShapes.put("sieve", sieve); + + CollisionShape smooth = makeSmoothHeightfield(); + namedShapes.put("smooth", smooth); + + { + float topRadius = 3f; // TODO randomize + float pedestalRadius = 0.3f; + CollisionShape table + = CompoundTestShapes.makeTable(topRadius, pedestalRadius); + namedShapes.put("table", table); + } + + { + float headRadius = 2f; // TODO randomize + float spikeRadius = 0.2f; + CollisionShape thumbTack + = CompoundTestShapes.makeThumbTack(headRadius, spikeRadius); + namedShapes.put("thumbTack", thumbTack); + } + + { + float bodyRadius = 1.5f; // TODO randomize + float handleRadius = 0.3f; + CollisionShape top + = CompoundTestShapes.makeTop(bodyRadius, handleRadius); + namedShapes.put("top", top); + } + + CollisionShape tray = CompoundTestShapes.makeTray(); + namedShapes.put("tray", tray); + + CollisionShape triangle = makeTriangle(); + namedShapes.put("triangle", triangle); + } + + /** + * Generate a bed-of-nails heightfield. Not intended for use in a dynamic + * body. + * + * @return a new heightfield shape (not null) + */ + public static HeightfieldCollisionShape makeBedOfNails() { + int n = 64; + float[] array = MinieTestTerrains.bedOfNailsArray(n); + + Vector3f scale = new Vector3f(40f / n, 4f, 40f / n); + int upAxis = PhysicsSpace.AXIS_Y; + boolean flipQuadEdges = false; + boolean flipTriangleWinding = false; + boolean useDiamond = true; + boolean useZigzag = false; + HeightfieldCollisionShape result = new HeightfieldCollisionShape( + n, n, array, scale, upAxis, flipQuadEdges, + flipTriangleWinding, useDiamond, useZigzag); + result.setContactFilterEnabled(false); // reduce risk of fallthru + + return result; + } + + /** + * Generate a dimpled heightfield. Not intended for use in a dynamic body. + * + * @return a new heightfield shape (not null) + */ + public static HeightfieldCollisionShape makeDimples() { + int n = 128; + float[] array = MinieTestTerrains.dimplesArray(n); + + Vector3f scale = new Vector3f(40f / n, 1f, 40f / n); + HeightfieldCollisionShape result + = new HeightfieldCollisionShape(array, scale); + result.setContactFilterEnabled(false); // reduce risk of fallthru + + return result; + } + + /** + * Generate a spherical dome or plano-convex lens. + * + * @param radius (in unscaled shape units, >0) + * @param verticalAngle the central angle from the top to the rim (in + * radians, <Pi, >0, Pi/2 → hemisphere) + * @return a new hull shape + */ + public static HullCollisionShape makeDome( + float radius, float verticalAngle) { + Validate.positive(radius, "radius"); + Validate.inRange(verticalAngle, "vertical angle", 0f, FastMath.PI); + + int rimSamples = 20; + int quadrantSamples = 10; + DomeMesh mesh = new DomeMesh(rimSamples, quadrantSamples); + mesh.setVerticalAngle(verticalAngle); + FloatBuffer buffer = mesh.getFloatBuffer(VertexBuffer.Type.Position); + + // Scale mesh positions to the desired radius. + int start = 0; + int end = buffer.limit(); + Vector3f scale = new Vector3f(radius, radius, radius); + MyBuffer.scale(buffer, start, end, scale); + + // Use max-min to center the vertices. + Vector3f max = new Vector3f(); + Vector3f min = new Vector3f(); + MyBuffer.maxMin(buffer, start, end, max, min); + Vector3f offset = MyVector3f.midpoint(min, max, null).negateLocal(); + MyBuffer.translate(buffer, start, end, offset); + + HullCollisionShape result = new HullCollisionShape(buffer); + + return result; + } + + /** + * Generate a (gridiron) football using overlapping spheres arranged in a + * row. + * + * @param midRadius the radius of the Y-Z cross section at X=0 (in unscaled + * shape units, ≥0) + * @return a new MultiSphere shape (not null) + */ + public static MultiSphere makeFootball(float midRadius) { + float genRadius = 2f * midRadius; // curvature radius of the generatrix + float endRadius = 0.5f * midRadius; // controls pointiness of the ends + + int numSpheres = 9; + List centers = new ArrayList<>(numSpheres); + List radii = new ArrayList<>(numSpheres); + + float centerY = genRadius - midRadius; + float maxX = FastMath.sqrt(genRadius * genRadius - centerY * centerY); + float lastCenterX = maxX - endRadius; + + float xStep = (2f * lastCenterX) / (numSpheres - 1); + for (int sphereI = 0; sphereI < numSpheres; ++sphereI) { + float centerX = -lastCenterX + sphereI * xStep; + // centerX varies from -lastCenterX to +lastCenterX + centers.add(new Vector3f(centerX, 0f, 0f)); + + float radius = genRadius - MyMath.hypotenuse(centerX, centerY); + radii.add(radius); + } + + MultiSphere result = new MultiSphere(centers, radii); + + return result; + } + + /** + * Generate a large rounded rectangle. + * + * @return a new convex 2-D shape (not null) + */ + public static Convex2dShape makeRoundedRectangle() { + float halfExtent = 30f; + + float x2 = halfExtent; + float y2 = halfExtent / MyMath.phi; + float x1 = -x2; + float y1 = -y2; + float cornerRadius = 4f; + float zNorm = 1f; + Mesh mesh = new RoundedRectangle(x1, x2, y1, y2, cornerRadius, zNorm); + HullCollisionShape hull = new HullCollisionShape(mesh); + Convex2dShape result = new Convex2dShape(hull); + + return result; + } + + /** + * Generate a smooth 64x64 heightfield. Not intended for use in a dynamic + * body. + * + * @return a new heightfield shape (not null) + */ + public static HeightfieldCollisionShape makeSmoothHeightfield() { + int n = 64; + float[] array = MinieTestTerrains.quadraticArray(n); + + Vector3f scale = new Vector3f(40f / n, 12.5f, 40f / n); + HeightfieldCollisionShape result + = new HeightfieldCollisionShape(array, scale); + result.setContactFilterEnabled(false); // reduce risk of fallthru + + return result; + } + + /** + * Generate a large isosceles triangle. + * + * @return a new simplex shape (not null) + */ + public static SimplexCollisionShape makeTriangle() { + float radius = 50f; + + float x = radius * MyMath.rootHalf; + Vector3f p1 = new Vector3f(x, 0f, x); + Vector3f p2 = new Vector3f(x, 0f, -x); + Vector3f p3 = new Vector3f(-x, 0f, 0f); + SimplexCollisionShape result = new SimplexCollisionShape(p1, p2, p3); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/ShapeGenerator.java b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/ShapeGenerator.java index 0b0d5b177..e43190232 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/ShapeGenerator.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/ShapeGenerator.java @@ -1,733 +1,733 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.shape; - -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MinkowskiSum; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.collision.shapes.SimplexCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.Mesh.Mode; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.RectangularSolid; -import jme3utilities.math.noise.Generator; -import jme3utilities.mesh.Cone; -import jme3utilities.mesh.Dodecahedron; -import jme3utilities.mesh.Icosahedron; -import jme3utilities.mesh.Octahedron; -import jme3utilities.mesh.Prism; - -/** - * Generate pseudo-random collision shapes for use in MinieExamples. - * - * @author Stephen Gold sgold@sonic.net - */ -public class ShapeGenerator extends Generator { - // ************************************************************************* - // constants and loggers - - /** - * square root of 3 - */ - final public static float root3 = FastMath.sqrt(3f); - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(ShapeGenerator.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a generator. - */ - public ShapeGenerator() { // explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Generate a box shape. - * - * @return a new shape (not null) - */ - public BoxCollisionShape nextBox() { - float rx = nextFloat(0.5f, 1.5f); - float ry = nextFloat(0.5f, 1.5f); - float rz = nextFloat(0.5f, 1.5f); - Vector3f halfExtents = new Vector3f(rx, ry, rz); - BoxCollisionShape result = new BoxCollisionShape(halfExtents); - - return result; - } - - /** - * Generate a capsule shape. - * - * @return a new shape (not null) - */ - public CapsuleCollisionShape nextCapsule() { - float radius = nextFloat(0.2f, 1.2f); - float height = nextFloat(0.5f, 1.5f); - int axis = nextInt(3); - CapsuleCollisionShape result - = new CapsuleCollisionShape(radius, height, axis); - - return result; - } - - /** - * Generate a cone shape. - * - * @return a new shape (not null) - */ - public ConeCollisionShape nextCone() { - float baseRadius = nextFloat(0.5f, 1.5f); - float height = nextFloat(0.5f, 2.5f); - int axisIndex = nextInt(3); - ConeCollisionShape result - = new ConeCollisionShape(baseRadius, height, axisIndex); - - return result; - } - - /** - * Generate a cone+box shape. - * - * @return a new Minkowski-sum shape (not null) - */ - public MinkowskiSum nextConeBox() { - BoxCollisionShape box = nextBox(); - float baseRadius = nextFloat(0.3f, 1f); - float height = nextFloat(0.5f, 1.5f); - ConeCollisionShape cone = new ConeCollisionShape(baseRadius, height); - MinkowskiSum result = new MinkowskiSum(cone, box); - - return result; - } - - /** - * Generate a cylinder shape. - * - * @return a new shape (not null) - */ - public CylinderCollisionShape nextCylinder() { - float baseRadius = nextFloat(0.5f, 1.5f); - float height = nextFloat(1f, 4f); - int axisIndex = nextInt(3); - CylinderCollisionShape result - = new CylinderCollisionShape(baseRadius, height, axisIndex); - - return result; - } - - /** - * Generate a cylinder+box shape. - * - * @return a new Minkowski-sum shape (not null) - */ - public MinkowskiSum nextCylinderBox() { - BoxCollisionShape box = nextBox(); - float baseRadius = nextFloat(0.3f, 1.5f); - float height = CollisionShape.getDefaultMargin(); - CylinderCollisionShape cylinder = new CylinderCollisionShape( - baseRadius, height, MyVector3f.yAxis); - MinkowskiSum result = new MinkowskiSum(cylinder, box); - - return result; - } - - /** - * Generate a spherical dome or plano-convex lens. - * - * @return a new shape - */ - public HullCollisionShape nextDome() { - float radius = nextFloat(0.7f, 1.7f); - float verticalAngle = nextFloat(0.7f, 2f); - HullCollisionShape result - = MinieTestShapes.makeDome(radius, verticalAngle); - - return result; - } - - /** - * Generate a (gridiron) football. - * - * @return a new shape - */ - public MultiSphere nextFootball() { - float midRadius = nextFloat(0.5f, 1.5f); - MultiSphere result = MinieTestShapes.makeFootball(midRadius); - - return result; - } - - /** - * Generate a 4-sphere shape, a box with rounded corners. - * - * @return a new shape (not null) - */ - public MultiSphere nextFourSphere() { - float rx = nextFloat(0.4f, 1f); - float ry = nextFloat(1f, 2f); - float rz = nextFloat(1f, 2f); - Vector3f halfExtents = new Vector3f(rx, ry, rz); - - RectangularSolid solid = new RectangularSolid(halfExtents); - MultiSphere result = new MultiSphere(solid); - - return result; - } - - /** - * Generate a rectangular frame. - * - * @return a new shape - */ - public CompoundCollisionShape nextFrame() { - float halfDepth = nextFloat(0.1f, 0.5f); - float ihHeight = nextFloat(0.7f, 2f); - float ihWidth = 1.6f * ihHeight; - float halfThickness = ihHeight * nextFloat(0.1f, 0.2f); - CompoundCollisionShape result = CompoundTestShapes.makeFrame( - ihHeight, ihWidth, halfDepth, halfThickness); - - return result; - } - - /** - * Approximate a Z-axis half-pipe shape. - * - * @return a new shape - */ - public CompoundCollisionShape nextHalfPipe() { - float innerRadius = nextFloat(0.5f, 1.5f); - float thickness = nextFloat(0.2f, 0.5f); - float length = nextFloat(1f, 4f); - float arc = FastMath.PI; - int numChildren = 20; - - CompoundCollisionShape result = CompoundTestShapes.makePipe( - innerRadius, thickness, length, arc, numChildren); - - return result; - } - - /** - * Generate a centered HullCollisionShape, using the origin plus 4-to-19 - * pseudo-random vertices. - * - * @return a new shape - */ - public HullCollisionShape nextHull() { - int numVertices = nextInt(5, 20); - - FloatBuffer buffer = BufferUtils.createFloatBuffer( - MyVector3f.numAxes * numVertices); - buffer.put(0f).put(0f).put(0f); - Vector3f tmpLocation = new Vector3f(); - for (int vertexI = 1; vertexI < numVertices; ++vertexI) { - nextUnitVector3f(tmpLocation); - tmpLocation.multLocal(1.5f); - buffer.put(tmpLocation.x).put(tmpLocation.y).put(tmpLocation.z); - } - - // Use arithmetic mean to center the vertices. - int start = 0; - int end = buffer.limit(); - Vector3f offset = MyBuffer.mean(buffer, start, end, null); - offset.negateLocal(); - MyBuffer.translate(buffer, start, end, offset); - - HullCollisionShape result = new HullCollisionShape(buffer); - - return result; - } - - /** - * Generate an I-Beam shape. - * - * @return a new compound shape (not null) - */ - public CompoundCollisionShape nextIBeam() { - float length = nextFloat(1f, 10f); - float flangeWidth = nextFloat(1f, 2f); - float beamHeight = nextFloat(1f, 2f); - float thickness = nextFloat(0.1f, 0.3f); - CompoundCollisionShape result = CompoundTestShapes.makeIBeam( - length, flangeWidth, beamHeight, thickness); - - return result; - } - - /** - * Generate a shape for a lidless box. - * - * @return a new compound shape (not null) - */ - public CompoundCollisionShape nextLidlessBox() { - float iHeight = nextFloat(2f, 4f); - float iWidth = nextFloat(2f, 4f); - float iDepth = nextFloat(1f, 2f); - float wallThickness = nextFloat(0.1f, 0.3f); - CompoundCollisionShape result = CompoundTestShapes.makeLidlessBox( - iHeight, iWidth, iDepth, wallThickness); - - return result; - } - - /** - * Generate a MultiSphere shape with 1-4 spheres. - * - * @return a new shape (not null) - */ - public MultiSphere nextMultiSphere() { - int numSpheres = nextInt(1, 4); - if (numSpheres == 4) { - MultiSphere result = nextFourSphere(); - return result; - } - - List centers = new ArrayList<>(numSpheres); - List radii = new ArrayList<>(numSpheres); - - // The first sphere is always centered. - centers.add(Vector3f.ZERO); - float mainRadius = nextFloat(0.8f, 1.4f); - radii.add(mainRadius); - - for (int sphereIndex = 1; sphereIndex < numSpheres; ++sphereIndex) { - // Add a smaller sphere, offset from the main one. - Vector3f offset = nextUnitVector3f(null); - offset.multLocal(mainRadius); - centers.add(offset); - - float radius = mainRadius * nextFloat(0.2f, 1f); - radii.add(radius); - } - - MultiSphere result = new MultiSphere(centers, radii); - - if (numSpheres == 1) { - // Scale the sphere to make an ellipsoid. - float xScale = nextFloat(1f, 2f); - float yScale = nextFloat(0.6f, 1.6f); - float zScale = nextFloat(0.4f, 1.4f); - result.setScale(new Vector3f(xScale, yScale, zScale)); - } - - return result; - } - - /** - * Generate a Platonic solid. - * - * @return a new shape (not null) - */ - public CollisionShape nextPlatonic() { - Mesh mesh; - float radius; - boolean noNormals = false; - - CollisionShape result; - int solidType = nextInt(0, 4); - switch (solidType) { - case 0: // regular tetrahedron - radius = 1.55f * nextFloat(0.5f, 1.5f); - float he = radius / root3; - Vector3f v0 = new Vector3f(-he, +he, +he); - Vector3f v1 = new Vector3f(+he, -he, +he); - Vector3f v2 = new Vector3f(+he, +he, -he); - Vector3f v3 = new Vector3f(-he, -he, -he); - result = new SimplexCollisionShape(v0, v1, v2, v3); - break; - - case 1: // cube or regular hexahedron - radius = 1.4f * nextFloat(0.5f, 1.5f); - float halfExtent = radius / root3; - result = new BoxCollisionShape(halfExtent); - break; - - case 2: // regular octahedron - radius = 1.4f * nextFloat(0.5f, 1.5f); - mesh = new Octahedron(radius, noNormals); - result = new HullCollisionShape(mesh); - break; - - case 3: // regular dodecahedron - radius = 1.1f * nextFloat(0.5f, 1.5f); - mesh = new Dodecahedron(radius, Mode.Triangles); - result = new HullCollisionShape(mesh); - break; - - case 4: // regular icosahedron - radius = 1.13f * nextFloat(0.5f, 1.5f); - mesh = new Icosahedron(radius, noNormals); - result = new HullCollisionShape(mesh); - break; - - default: - throw new RuntimeException("solidType = " + solidType); - } - - return result; - } - - /** - * Generate a prism. - * - * @return a new shape (not null) - */ - public HullCollisionShape nextPrism() { - int numSides = nextInt(3, 9); - float radius = nextFloat(0.6f, 2f); - float height = nextFloat(0.6f, 1.6f); - boolean noNormals = false; - Mesh mesh = new Prism(numSides, radius, height, noNormals); - HullCollisionShape result = new HullCollisionShape(mesh); - - return result; - } - - /** - * Generate a pyramid. - * - * @return a new shape (not null) - */ - public HullCollisionShape nextPyramid() { - int numSides = nextInt(3, 9); - float baseRadius = nextFloat(0.8f, 1.8f); - float yHeight = nextFloat(1f, 2.5f); - boolean generatePyramid = true; - Mesh mesh = new Cone(numSides, baseRadius, yHeight, generatePyramid); - HullCollisionShape result = new HullCollisionShape(mesh); - - return result; - } - - /** - * Generate a rounded disc shape. - * - * @return a new Minkowski-sum shape (not null) - */ - public MinkowskiSum nextRoundedDisc() { - float baseRadius = nextFloat(0.3f, 1.5f); - float height = nextFloat(0.5f, 1f); - CylinderCollisionShape thinDisc = new CylinderCollisionShape( - baseRadius, 0.04f, MyVector3f.yAxis); - SphereCollisionShape sphere = new SphereCollisionShape(height / 2f); - MinkowskiSum result = new MinkowskiSum(thinDisc, sphere); - - return result; - } - - /** - * Generate a flying-saucer shape. - * - * @return a new Minkowski-sum shape (not null) - */ - public MinkowskiSum nextSaucer() { - float baseRadius = nextFloat(0.3f, 1f); - float height = nextFloat(0.3f, 1f); - ConeCollisionShape cone = new ConeCollisionShape(baseRadius, height); - MinkowskiSum result = new MinkowskiSum(cone, cone); - - return result; - } - - /** - * Generate an instance of the named shape. - * - * @param shapeName the type of shape to generate (not null, not empty) - * @return a new shape (not null) - */ - public CollisionShape nextShape(String shapeName) { - Validate.nonEmpty(shapeName, "shape name"); - - CollisionShape result; - switch (shapeName) { - case "box": - result = nextBox(); - break; - - case "capsule": - result = nextCapsule(); - break; - - case "cone": - result = nextCone(); - break; - - case "coneBox": - result = nextConeBox(); - break; - - case "cylinder": - result = nextCylinder(); - break; - - case "cylinderBox": - result = nextCylinderBox(); - break; - - case "dome": - result = nextDome(); - break; - - case "football": - result = nextFootball(); - break; - - case "frame": - result = nextFrame(); - break; - - case "halfPipe": - result = nextHalfPipe(); - break; - - case "hull": - result = nextHull(); - break; - - case "iBeam": - result = nextIBeam(); - break; - - case "lidlessBox": - result = nextLidlessBox(); - break; - - case "multiSphere": - result = nextMultiSphere(); - break; - - case "platonic": - result = nextPlatonic(); - break; - - case "prism": - result = nextPrism(); - break; - - case "pyramid": - result = nextPyramid(); - break; - - case "roundedDisc": - result = nextRoundedDisc(); - break; - - case "saucer": - result = nextSaucer(); - break; - - case "snowman": - result = nextSnowman(); - break; - - case "sphere": - result = nextSphere(); - break; - - case "star": - result = nextStar(); - break; - - case "tetrahedron": - result = nextTetrahedron(); - break; - - case "torus": - result = nextTorus(); - break; - - case "triangularFrame": - result = nextTriangularFrame(); - break; - - case "trident": - result = nextTrident(); - break; - - case "washer": - result = nextWasher(); - break; - - default: - String message = "shapeName = " + MyString.quote(shapeName); - throw new IllegalArgumentException(message); - } - - return result; - } - - /** - * Generate a 3-ball snowman shape with the head on the +Y axis. - * - * @return a new compound shape (not null) - */ - public CompoundCollisionShape nextSnowman() { - float baseRadius = nextFloat(0.7f, 1.5f); - CompoundCollisionShape result - = CompoundTestShapes.makeSnowman(baseRadius); - - return result; - } - - /** - * Generate a sphere shape. - * - * @return a new shape (not null) - */ - public SphereCollisionShape nextSphere() { - float radius = nextFloat(0.5f, 1.5f); - SphereCollisionShape result = new SphereCollisionShape(radius); - - return result; - } - - /** - * Generate a star-shaped compound shape. - * - * @return a new shape (not null) - */ - public CompoundCollisionShape nextStar() { - float centerY = nextFloat(0.3f, 0.6f); - float outerRadius = nextFloat(1f, 2.5f); - int numPoints = nextInt(4, 9); - float radiusRatio = nextFloat(0.2f, 0.7f); - int numTriangles = 4 + 2 * nextInt(0, 1); - CompoundCollisionShape result = CompoundTestShapes.makeStar( - numPoints, outerRadius, centerY, radiusRatio, numTriangles); - - return result; - } - - /** - * Generate a tetrahedral SimplexCollisionShape. - * - * @return a new shape (not null) - */ - public SimplexCollisionShape nextTetrahedron() { - float r1 = nextFloat(0.4f, 1.6f); - float r2 = nextFloat(0.4f, 1.6f); - float r3 = nextFloat(0.4f, 1.6f); - float r4 = nextFloat(0.4f, 1.6f); - - Vector3f p1 = new Vector3f(r1, r1, r1); - Vector3f p2 = new Vector3f(r2, -r2, -r2); - Vector3f p3 = new Vector3f(-r3, -r3, r3); - Vector3f p4 = new Vector3f(-r4, r4, -r4); - SimplexCollisionShape result - = new SimplexCollisionShape(p1, p2, p3, p4); - - return result; - } - - /** - * Approximate a torus or donut, open on the Z axis. - * - * @return a new shape - */ - public CompoundCollisionShape nextTorus() { - float majorRadius = nextFloat(1f, 1.5f); - float minorRadius = nextFloat(0.2f, 0.6f); - CompoundCollisionShape result - = CompoundTestShapes.makeTorus(majorRadius, minorRadius); - - return result; - } - - /** - * Generate a triangular frame with identical sides, open on the Z axis. - * - * @return a new compound shape (not null) - */ - public CompoundCollisionShape nextTriangularFrame() { - float internalLength = nextFloat(2f, 6f); - float innerR = internalLength / root3; - float depth = nextFloat(0.2f, 1f); - float thickness = internalLength * nextFloat(0.1f, 0.2f); - float arc = FastMath.TWO_PI; - int numSegments = 3; - CompoundCollisionShape result = CompoundTestShapes.makePipe( - innerR, thickness, depth, arc, numSegments); - - return result; - } - - /** - * Generate a trident shape. - * - * @return a new compound shape (not null) - */ - public CompoundCollisionShape nextTrident() { - float shaftLength = nextFloat(4f, 12f); - float shaftRadius = nextFloat(0.1f, 0.2f); - CompoundCollisionShape result - = CompoundTestShapes.makeTrident(shaftLength, shaftRadius); - - return result; - } - - /** - * Approximate a flat washer (or flat ring), open on the Z axis. - * - * @return a new compound shape (not null) - */ - public CompoundCollisionShape nextWasher() { - float innerRadius = nextFloat(0.6f, 1.5f); - float outerRadius = innerRadius + nextFloat(0.8f, 1.5f); - float zThickness = nextFloat(0.2f, 0.4f); - float arc = FastMath.TWO_PI; - int numChildren = 24; - - CompoundCollisionShape result = CompoundTestShapes.makePipe(innerRadius, - outerRadius - innerRadius, zThickness, arc, numChildren); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.shape; + +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.ConeCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MinkowskiSum; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.collision.shapes.SimplexCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.Mesh.Mode; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.RectangularSolid; +import jme3utilities.math.noise.Generator; +import jme3utilities.mesh.Cone; +import jme3utilities.mesh.Dodecahedron; +import jme3utilities.mesh.Icosahedron; +import jme3utilities.mesh.Octahedron; +import jme3utilities.mesh.Prism; + +/** + * Generate pseudo-random collision shapes for use in MinieExamples. + * + * @author Stephen Gold sgold@sonic.net + */ +public class ShapeGenerator extends Generator { + // ************************************************************************* + // constants and loggers + + /** + * square root of 3 + */ + final public static float root3 = FastMath.sqrt(3f); + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(ShapeGenerator.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a generator. + */ + public ShapeGenerator() { // explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Generate a box shape. + * + * @return a new shape (not null) + */ + public BoxCollisionShape nextBox() { + float rx = nextFloat(0.5f, 1.5f); + float ry = nextFloat(0.5f, 1.5f); + float rz = nextFloat(0.5f, 1.5f); + Vector3f halfExtents = new Vector3f(rx, ry, rz); + BoxCollisionShape result = new BoxCollisionShape(halfExtents); + + return result; + } + + /** + * Generate a capsule shape. + * + * @return a new shape (not null) + */ + public CapsuleCollisionShape nextCapsule() { + float radius = nextFloat(0.2f, 1.2f); + float height = nextFloat(0.5f, 1.5f); + int axis = nextInt(3); + CapsuleCollisionShape result + = new CapsuleCollisionShape(radius, height, axis); + + return result; + } + + /** + * Generate a cone shape. + * + * @return a new shape (not null) + */ + public ConeCollisionShape nextCone() { + float baseRadius = nextFloat(0.5f, 1.5f); + float height = nextFloat(0.5f, 2.5f); + int axisIndex = nextInt(3); + ConeCollisionShape result + = new ConeCollisionShape(baseRadius, height, axisIndex); + + return result; + } + + /** + * Generate a cone+box shape. + * + * @return a new Minkowski-sum shape (not null) + */ + public MinkowskiSum nextConeBox() { + BoxCollisionShape box = nextBox(); + float baseRadius = nextFloat(0.3f, 1f); + float height = nextFloat(0.5f, 1.5f); + ConeCollisionShape cone = new ConeCollisionShape(baseRadius, height); + MinkowskiSum result = new MinkowskiSum(cone, box); + + return result; + } + + /** + * Generate a cylinder shape. + * + * @return a new shape (not null) + */ + public CylinderCollisionShape nextCylinder() { + float baseRadius = nextFloat(0.5f, 1.5f); + float height = nextFloat(1f, 4f); + int axisIndex = nextInt(3); + CylinderCollisionShape result + = new CylinderCollisionShape(baseRadius, height, axisIndex); + + return result; + } + + /** + * Generate a cylinder+box shape. + * + * @return a new Minkowski-sum shape (not null) + */ + public MinkowskiSum nextCylinderBox() { + BoxCollisionShape box = nextBox(); + float baseRadius = nextFloat(0.3f, 1.5f); + float height = CollisionShape.getDefaultMargin(); + CylinderCollisionShape cylinder = new CylinderCollisionShape( + baseRadius, height, MyVector3f.yAxis); + MinkowskiSum result = new MinkowskiSum(cylinder, box); + + return result; + } + + /** + * Generate a spherical dome or plano-convex lens. + * + * @return a new shape + */ + public HullCollisionShape nextDome() { + float radius = nextFloat(0.7f, 1.7f); + float verticalAngle = nextFloat(0.7f, 2f); + HullCollisionShape result + = MinieTestShapes.makeDome(radius, verticalAngle); + + return result; + } + + /** + * Generate a (gridiron) football. + * + * @return a new shape + */ + public MultiSphere nextFootball() { + float midRadius = nextFloat(0.5f, 1.5f); + MultiSphere result = MinieTestShapes.makeFootball(midRadius); + + return result; + } + + /** + * Generate a 4-sphere shape, a box with rounded corners. + * + * @return a new shape (not null) + */ + public MultiSphere nextFourSphere() { + float rx = nextFloat(0.4f, 1f); + float ry = nextFloat(1f, 2f); + float rz = nextFloat(1f, 2f); + Vector3f halfExtents = new Vector3f(rx, ry, rz); + + RectangularSolid solid = new RectangularSolid(halfExtents); + MultiSphere result = new MultiSphere(solid); + + return result; + } + + /** + * Generate a rectangular frame. + * + * @return a new shape + */ + public CompoundCollisionShape nextFrame() { + float halfDepth = nextFloat(0.1f, 0.5f); + float ihHeight = nextFloat(0.7f, 2f); + float ihWidth = 1.6f * ihHeight; + float halfThickness = ihHeight * nextFloat(0.1f, 0.2f); + CompoundCollisionShape result = CompoundTestShapes.makeFrame( + ihHeight, ihWidth, halfDepth, halfThickness); + + return result; + } + + /** + * Approximate a Z-axis half-pipe shape. + * + * @return a new shape + */ + public CompoundCollisionShape nextHalfPipe() { + float innerRadius = nextFloat(0.5f, 1.5f); + float thickness = nextFloat(0.2f, 0.5f); + float length = nextFloat(1f, 4f); + float arc = FastMath.PI; + int numChildren = 20; + + CompoundCollisionShape result = CompoundTestShapes.makePipe( + innerRadius, thickness, length, arc, numChildren); + + return result; + } + + /** + * Generate a centered HullCollisionShape, using the origin plus 4-to-19 + * pseudo-random vertices. + * + * @return a new shape + */ + public HullCollisionShape nextHull() { + int numVertices = nextInt(5, 20); + + FloatBuffer buffer = BufferUtils.createFloatBuffer( + MyVector3f.numAxes * numVertices); + buffer.put(0f).put(0f).put(0f); + Vector3f tmpLocation = new Vector3f(); + for (int vertexI = 1; vertexI < numVertices; ++vertexI) { + nextUnitVector3f(tmpLocation); + tmpLocation.multLocal(1.5f); + buffer.put(tmpLocation.x).put(tmpLocation.y).put(tmpLocation.z); + } + + // Use arithmetic mean to center the vertices. + int start = 0; + int end = buffer.limit(); + Vector3f offset = MyBuffer.mean(buffer, start, end, null); + offset.negateLocal(); + MyBuffer.translate(buffer, start, end, offset); + + HullCollisionShape result = new HullCollisionShape(buffer); + + return result; + } + + /** + * Generate an I-Beam shape. + * + * @return a new compound shape (not null) + */ + public CompoundCollisionShape nextIBeam() { + float length = nextFloat(1f, 10f); + float flangeWidth = nextFloat(1f, 2f); + float beamHeight = nextFloat(1f, 2f); + float thickness = nextFloat(0.1f, 0.3f); + CompoundCollisionShape result = CompoundTestShapes.makeIBeam( + length, flangeWidth, beamHeight, thickness); + + return result; + } + + /** + * Generate a shape for a lidless box. + * + * @return a new compound shape (not null) + */ + public CompoundCollisionShape nextLidlessBox() { + float iHeight = nextFloat(2f, 4f); + float iWidth = nextFloat(2f, 4f); + float iDepth = nextFloat(1f, 2f); + float wallThickness = nextFloat(0.1f, 0.3f); + CompoundCollisionShape result = CompoundTestShapes.makeLidlessBox( + iHeight, iWidth, iDepth, wallThickness); + + return result; + } + + /** + * Generate a MultiSphere shape with 1-4 spheres. + * + * @return a new shape (not null) + */ + public MultiSphere nextMultiSphere() { + int numSpheres = nextInt(1, 4); + if (numSpheres == 4) { + MultiSphere result = nextFourSphere(); + return result; + } + + List centers = new ArrayList<>(numSpheres); + List radii = new ArrayList<>(numSpheres); + + // The first sphere is always centered. + centers.add(Vector3f.ZERO); + float mainRadius = nextFloat(0.8f, 1.4f); + radii.add(mainRadius); + + for (int sphereIndex = 1; sphereIndex < numSpheres; ++sphereIndex) { + // Add a smaller sphere, offset from the main one. + Vector3f offset = nextUnitVector3f(null); + offset.multLocal(mainRadius); + centers.add(offset); + + float radius = mainRadius * nextFloat(0.2f, 1f); + radii.add(radius); + } + + MultiSphere result = new MultiSphere(centers, radii); + + if (numSpheres == 1) { + // Scale the sphere to make an ellipsoid. + float xScale = nextFloat(1f, 2f); + float yScale = nextFloat(0.6f, 1.6f); + float zScale = nextFloat(0.4f, 1.4f); + result.setScale(new Vector3f(xScale, yScale, zScale)); + } + + return result; + } + + /** + * Generate a Platonic solid. + * + * @return a new shape (not null) + */ + public CollisionShape nextPlatonic() { + Mesh mesh; + float radius; + boolean noNormals = false; + + CollisionShape result; + int solidType = nextInt(0, 4); + switch (solidType) { + case 0: // regular tetrahedron + radius = 1.55f * nextFloat(0.5f, 1.5f); + float he = radius / root3; + Vector3f v0 = new Vector3f(-he, +he, +he); + Vector3f v1 = new Vector3f(+he, -he, +he); + Vector3f v2 = new Vector3f(+he, +he, -he); + Vector3f v3 = new Vector3f(-he, -he, -he); + result = new SimplexCollisionShape(v0, v1, v2, v3); + break; + + case 1: // cube or regular hexahedron + radius = 1.4f * nextFloat(0.5f, 1.5f); + float halfExtent = radius / root3; + result = new BoxCollisionShape(halfExtent); + break; + + case 2: // regular octahedron + radius = 1.4f * nextFloat(0.5f, 1.5f); + mesh = new Octahedron(radius, noNormals); + result = new HullCollisionShape(mesh); + break; + + case 3: // regular dodecahedron + radius = 1.1f * nextFloat(0.5f, 1.5f); + mesh = new Dodecahedron(radius, Mode.Triangles); + result = new HullCollisionShape(mesh); + break; + + case 4: // regular icosahedron + radius = 1.13f * nextFloat(0.5f, 1.5f); + mesh = new Icosahedron(radius, noNormals); + result = new HullCollisionShape(mesh); + break; + + default: + throw new RuntimeException("solidType = " + solidType); + } + + return result; + } + + /** + * Generate a prism. + * + * @return a new shape (not null) + */ + public HullCollisionShape nextPrism() { + int numSides = nextInt(3, 9); + float radius = nextFloat(0.6f, 2f); + float height = nextFloat(0.6f, 1.6f); + boolean noNormals = false; + Mesh mesh = new Prism(numSides, radius, height, noNormals); + HullCollisionShape result = new HullCollisionShape(mesh); + + return result; + } + + /** + * Generate a pyramid. + * + * @return a new shape (not null) + */ + public HullCollisionShape nextPyramid() { + int numSides = nextInt(3, 9); + float baseRadius = nextFloat(0.8f, 1.8f); + float yHeight = nextFloat(1f, 2.5f); + boolean generatePyramid = true; + Mesh mesh = new Cone(numSides, baseRadius, yHeight, generatePyramid); + HullCollisionShape result = new HullCollisionShape(mesh); + + return result; + } + + /** + * Generate a rounded disc shape. + * + * @return a new Minkowski-sum shape (not null) + */ + public MinkowskiSum nextRoundedDisc() { + float baseRadius = nextFloat(0.3f, 1.5f); + float height = nextFloat(0.5f, 1f); + CylinderCollisionShape thinDisc = new CylinderCollisionShape( + baseRadius, 0.04f, MyVector3f.yAxis); + SphereCollisionShape sphere = new SphereCollisionShape(height / 2f); + MinkowskiSum result = new MinkowskiSum(thinDisc, sphere); + + return result; + } + + /** + * Generate a flying-saucer shape. + * + * @return a new Minkowski-sum shape (not null) + */ + public MinkowskiSum nextSaucer() { + float baseRadius = nextFloat(0.3f, 1f); + float height = nextFloat(0.3f, 1f); + ConeCollisionShape cone = new ConeCollisionShape(baseRadius, height); + MinkowskiSum result = new MinkowskiSum(cone, cone); + + return result; + } + + /** + * Generate an instance of the named shape. + * + * @param shapeName the type of shape to generate (not null, not empty) + * @return a new shape (not null) + */ + public CollisionShape nextShape(String shapeName) { + Validate.nonEmpty(shapeName, "shape name"); + + CollisionShape result; + switch (shapeName) { + case "box": + result = nextBox(); + break; + + case "capsule": + result = nextCapsule(); + break; + + case "cone": + result = nextCone(); + break; + + case "coneBox": + result = nextConeBox(); + break; + + case "cylinder": + result = nextCylinder(); + break; + + case "cylinderBox": + result = nextCylinderBox(); + break; + + case "dome": + result = nextDome(); + break; + + case "football": + result = nextFootball(); + break; + + case "frame": + result = nextFrame(); + break; + + case "halfPipe": + result = nextHalfPipe(); + break; + + case "hull": + result = nextHull(); + break; + + case "iBeam": + result = nextIBeam(); + break; + + case "lidlessBox": + result = nextLidlessBox(); + break; + + case "multiSphere": + result = nextMultiSphere(); + break; + + case "platonic": + result = nextPlatonic(); + break; + + case "prism": + result = nextPrism(); + break; + + case "pyramid": + result = nextPyramid(); + break; + + case "roundedDisc": + result = nextRoundedDisc(); + break; + + case "saucer": + result = nextSaucer(); + break; + + case "snowman": + result = nextSnowman(); + break; + + case "sphere": + result = nextSphere(); + break; + + case "star": + result = nextStar(); + break; + + case "tetrahedron": + result = nextTetrahedron(); + break; + + case "torus": + result = nextTorus(); + break; + + case "triangularFrame": + result = nextTriangularFrame(); + break; + + case "trident": + result = nextTrident(); + break; + + case "washer": + result = nextWasher(); + break; + + default: + String message = "shapeName = " + MyString.quote(shapeName); + throw new IllegalArgumentException(message); + } + + return result; + } + + /** + * Generate a 3-ball snowman shape with the head on the +Y axis. + * + * @return a new compound shape (not null) + */ + public CompoundCollisionShape nextSnowman() { + float baseRadius = nextFloat(0.7f, 1.5f); + CompoundCollisionShape result + = CompoundTestShapes.makeSnowman(baseRadius); + + return result; + } + + /** + * Generate a sphere shape. + * + * @return a new shape (not null) + */ + public SphereCollisionShape nextSphere() { + float radius = nextFloat(0.5f, 1.5f); + SphereCollisionShape result = new SphereCollisionShape(radius); + + return result; + } + + /** + * Generate a star-shaped compound shape. + * + * @return a new shape (not null) + */ + public CompoundCollisionShape nextStar() { + float centerY = nextFloat(0.3f, 0.6f); + float outerRadius = nextFloat(1f, 2.5f); + int numPoints = nextInt(4, 9); + float radiusRatio = nextFloat(0.2f, 0.7f); + int numTriangles = 4 + 2 * nextInt(0, 1); + CompoundCollisionShape result = CompoundTestShapes.makeStar( + numPoints, outerRadius, centerY, radiusRatio, numTriangles); + + return result; + } + + /** + * Generate a tetrahedral SimplexCollisionShape. + * + * @return a new shape (not null) + */ + public SimplexCollisionShape nextTetrahedron() { + float r1 = nextFloat(0.4f, 1.6f); + float r2 = nextFloat(0.4f, 1.6f); + float r3 = nextFloat(0.4f, 1.6f); + float r4 = nextFloat(0.4f, 1.6f); + + Vector3f p1 = new Vector3f(r1, r1, r1); + Vector3f p2 = new Vector3f(r2, -r2, -r2); + Vector3f p3 = new Vector3f(-r3, -r3, r3); + Vector3f p4 = new Vector3f(-r4, r4, -r4); + SimplexCollisionShape result + = new SimplexCollisionShape(p1, p2, p3, p4); + + return result; + } + + /** + * Approximate a torus or donut, open on the Z axis. + * + * @return a new shape + */ + public CompoundCollisionShape nextTorus() { + float majorRadius = nextFloat(1f, 1.5f); + float minorRadius = nextFloat(0.2f, 0.6f); + CompoundCollisionShape result + = CompoundTestShapes.makeTorus(majorRadius, minorRadius); + + return result; + } + + /** + * Generate a triangular frame with identical sides, open on the Z axis. + * + * @return a new compound shape (not null) + */ + public CompoundCollisionShape nextTriangularFrame() { + float internalLength = nextFloat(2f, 6f); + float innerR = internalLength / root3; + float depth = nextFloat(0.2f, 1f); + float thickness = internalLength * nextFloat(0.1f, 0.2f); + float arc = FastMath.TWO_PI; + int numSegments = 3; + CompoundCollisionShape result = CompoundTestShapes.makePipe( + innerR, thickness, depth, arc, numSegments); + + return result; + } + + /** + * Generate a trident shape. + * + * @return a new compound shape (not null) + */ + public CompoundCollisionShape nextTrident() { + float shaftLength = nextFloat(4f, 12f); + float shaftRadius = nextFloat(0.1f, 0.2f); + CompoundCollisionShape result + = CompoundTestShapes.makeTrident(shaftLength, shaftRadius); + + return result; + } + + /** + * Approximate a flat washer (or flat ring), open on the Z axis. + * + * @return a new compound shape (not null) + */ + public CompoundCollisionShape nextWasher() { + float innerRadius = nextFloat(0.6f, 1.5f); + float outerRadius = innerRadius + nextFloat(0.8f, 1.5f); + float zThickness = nextFloat(0.2f, 0.4f); + float arc = FastMath.TWO_PI; + int numChildren = 24; + + CompoundCollisionShape result = CompoundTestShapes.makePipe(innerRadius, + outerRadius - innerRadius, zThickness, arc, numChildren); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/package-info.java index fa3045d4b..245d28b35 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/shape/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/shape/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2019, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes that generate collision shapes used in the MinieExamples subproject. - */ -package jme3utilities.minie.test.shape; +/* + Copyright (c) 2019, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes that generate collision shapes used in the MinieExamples subproject. + */ +package jme3utilities.minie.test.shape; diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/MinieTestTerrains.java b/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/MinieTestTerrains.java index 0f0fd5b0a..f689052cb 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/MinieTestTerrains.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/MinieTestTerrains.java @@ -1,280 +1,280 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.terrain; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.TextureKey; -import com.jme3.math.FastMath; -import com.jme3.terrain.geomipmap.TerrainQuad; -import com.jme3.terrain.heightmap.AbstractHeightMap; -import com.jme3.terrain.heightmap.HeightMap; -import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import java.util.logging.Logger; -import jme3utilities.Validate; -import jme3utilities.math.MyMath; - -/** - * Generate some interesting height arrays and terrains for use in - * MinieExamples. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MinieTestTerrains { - // ************************************************************************* - // constants and loggers - - /** - * 3x3 height array - */ - final private static float[] nineHeights = { - 1f, 0f, 1f, - 0f, 0.5f, 0f, - 1f, 0f, 1f - }; - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(MinieTestTerrains.class.getName()); - // ************************************************************************* - // fields - - /** - * 513x513 quad - */ - public static TerrainQuad largeQuad; - /** - * 17x17 quad - */ - public static TerrainQuad quad17x17; - /** - * 33x33 quad - */ - public static TerrainQuad quad33x33; - /** - * 5x5 quad - */ - public static TerrainQuad quad5x5; - /** - * 65x65 quad - */ - public static TerrainQuad quad65x65; - /** - * 9x9 quad - */ - public static TerrainQuad quad9x9; - /** - * 3x3 quad - */ - public static TerrainQuad smallQuad; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MinieTestTerrains() { - } - // ************************************************************************* - // new methods exposed - - /** - * Generate a square height array for a bed of nails. - * - * @param size the desired size (in rows or columns, ≥2) - * @return a new array - */ - public static float[] bedOfNailsArray(int size) { - Validate.inRange(size, "size", 2, Integer.MAX_VALUE); - - float[] result = new float[size * size]; - for (int rowIndex = 0; rowIndex < size; ++rowIndex) { - for (int columnIndex = 0; columnIndex < size; ++columnIndex) { - boolean nail = (MyMath.modulo(rowIndex + columnIndex, 8) == 3 - && MyMath.modulo(rowIndex - columnIndex, 8) == 1); - - int floatIndex = size * rowIndex + columnIndex; - result[floatIndex] = nail ? 1f : 0f; - } - } - - return result; - } - - /** - * Generate a square height array for a dimpled surface. - * - * @param size the desired size (in rows or columns, ≥2) - * @return a new array - */ - public static float[] dimplesArray(int size) { - Validate.inRange(size, "size", 2, Integer.MAX_VALUE); - - float dimpleDepth = 3f; - float sphereRadius = 20f; - int halfSpacing = 16; - - float sphereR2 = sphereRadius * sphereRadius; - float hThreshold = sphereRadius - dimpleDepth; - float[] result = new float[size * size]; - int spacing = 2 * halfSpacing; // between dimples - - for (int rowIndex = 0; rowIndex < size; ++rowIndex) { - float x = MyMath.modulo(rowIndex, spacing) - halfSpacing; - for (int columnIndex = 0; columnIndex < size; ++columnIndex) { - float y = MyMath.modulo(columnIndex, spacing) - halfSpacing; - float xy2 = x * x + y * y; - - float height = 0f; - if (xy2 < sphereR2) { - float h = FastMath.sqrt(sphereR2 - xy2); - if (h > hThreshold) { - height = hThreshold - h; - } - } - - int floatIndex = size * rowIndex + columnIndex; - result[floatIndex] = height; - } - } - - return result; - } - - /** - * Initialize the terrain quads during startup. - * - * @param assetManager (not null) - */ - public static void initialize(AssetManager assetManager) { - HeightMap heightMap = loadHeightMap(assetManager); - int terrainDiameter = heightMap.getSize(); - int mapSize = terrainDiameter + 1; // number of samples on a side - float[] heightArray = heightMap.getHeightMap(); - int patchSize = 33; // number of samples on a side - largeQuad = new TerrainQuad( - "large terrain", patchSize, mapSize, heightArray); - - patchSize = 3; - mapSize = patchSize; - smallQuad = new TerrainQuad( - "small terrain", patchSize, mapSize, nineHeights); - - patchSize = 5; - float[] heights25 = new float[patchSize * patchSize]; - for (int i = 0; i < patchSize * patchSize; ++i) { - heights25[i] = (i % 3) / 2f; - } - mapSize = patchSize; - quad5x5 = new TerrainQuad( - "5x5 terrain", patchSize, mapSize, heights25); - - patchSize = 9; - float[] heights81 = new float[patchSize * patchSize]; - for (int i = 0; i < patchSize * patchSize; ++i) { - heights81[i] = (i % 5) / 4f; - } - mapSize = patchSize; - quad9x9 = new TerrainQuad( - "9x9 terrain", patchSize, mapSize, heights81); - - patchSize = 17; - mapSize = patchSize; - float[] heights289 = new float[patchSize * patchSize]; - for (int i = 0; i < patchSize * patchSize; ++i) { - heights289[i] = (i % 3) / 2f; - } - quad17x17 = new TerrainQuad( - "17x17 terrain", patchSize, mapSize, heights289); - - patchSize = 33; - float[] heights1089 = new float[patchSize * patchSize]; - for (int i = 0; i < patchSize * patchSize; ++i) { - heights1089[i] = (i % 5) / 4f; - } - mapSize = patchSize; - quad33x33 = new TerrainQuad( - "33x33 terrain", patchSize, mapSize, heights1089); - - patchSize = 65; - float[] heights4225 = new float[patchSize * patchSize]; - for (int i = 0; i < patchSize * patchSize; ++i) { - heights4225[i] = (i % 3) / 2f; - } - mapSize = patchSize; - quad65x65 = new TerrainQuad( - "65x65 terrain", patchSize, mapSize, heights4225); - } - - /** - * Load a 513x513 height map from a texture asset. - * - * @param assetManager (not null) - * @return a new instance - */ - public static HeightMap loadHeightMap(AssetManager assetManager) { - boolean flipY = false; - TextureKey key = new TextureKey( - "Textures/BumpMapTest/Simple_height.png", flipY); - Texture texture = assetManager.loadTexture(key); - Image image = texture.getImage(); - - float heightScale = 1f; - AbstractHeightMap result = new ImageBasedHeightMap(image, heightScale); - result.load(); - - return result; - } - - /** - * Generate a square height array for a quadratic surface of revolution. - * - * @param size the desired size (in rows or columns, ≥2) - * @return a new array - */ - public static float[] quadraticArray(int size) { - Validate.inRange(size, "size", 2, Integer.MAX_VALUE); - - float halfNm1 = (size - 1) / 2f; - float[] result = new float[size * size]; - for (int rowIndex = 0; rowIndex < size; ++rowIndex) { - float x = -1f + rowIndex / halfNm1; // -1 .. +1 - for (int columnIndex = 0; columnIndex < size; ++columnIndex) { - float y = -1f + columnIndex / halfNm1; // -1 .. +1 - float r = MyMath.hypotenuse(x, y); - float height = -0.4f + (r - 0.8f) * (r - 0.8f); - - int floatIndex = size * rowIndex + columnIndex; - result[floatIndex] = height; - } - } - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.terrain; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.math.MyMath; + +/** + * Generate some interesting height arrays and terrains for use in + * MinieExamples. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MinieTestTerrains { + // ************************************************************************* + // constants and loggers + + /** + * 3x3 height array + */ + final private static float[] nineHeights = { + 1f, 0f, 1f, + 0f, 0.5f, 0f, + 1f, 0f, 1f + }; + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(MinieTestTerrains.class.getName()); + // ************************************************************************* + // fields + + /** + * 513x513 quad + */ + public static TerrainQuad largeQuad; + /** + * 17x17 quad + */ + public static TerrainQuad quad17x17; + /** + * 33x33 quad + */ + public static TerrainQuad quad33x33; + /** + * 5x5 quad + */ + public static TerrainQuad quad5x5; + /** + * 65x65 quad + */ + public static TerrainQuad quad65x65; + /** + * 9x9 quad + */ + public static TerrainQuad quad9x9; + /** + * 3x3 quad + */ + public static TerrainQuad smallQuad; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MinieTestTerrains() { + } + // ************************************************************************* + // new methods exposed + + /** + * Generate a square height array for a bed of nails. + * + * @param size the desired size (in rows or columns, ≥2) + * @return a new array + */ + public static float[] bedOfNailsArray(int size) { + Validate.inRange(size, "size", 2, Integer.MAX_VALUE); + + float[] result = new float[size * size]; + for (int rowIndex = 0; rowIndex < size; ++rowIndex) { + for (int columnIndex = 0; columnIndex < size; ++columnIndex) { + boolean nail = (MyMath.modulo(rowIndex + columnIndex, 8) == 3 + && MyMath.modulo(rowIndex - columnIndex, 8) == 1); + + int floatIndex = size * rowIndex + columnIndex; + result[floatIndex] = nail ? 1f : 0f; + } + } + + return result; + } + + /** + * Generate a square height array for a dimpled surface. + * + * @param size the desired size (in rows or columns, ≥2) + * @return a new array + */ + public static float[] dimplesArray(int size) { + Validate.inRange(size, "size", 2, Integer.MAX_VALUE); + + float dimpleDepth = 3f; + float sphereRadius = 20f; + int halfSpacing = 16; + + float sphereR2 = sphereRadius * sphereRadius; + float hThreshold = sphereRadius - dimpleDepth; + float[] result = new float[size * size]; + int spacing = 2 * halfSpacing; // between dimples + + for (int rowIndex = 0; rowIndex < size; ++rowIndex) { + float x = MyMath.modulo(rowIndex, spacing) - halfSpacing; + for (int columnIndex = 0; columnIndex < size; ++columnIndex) { + float y = MyMath.modulo(columnIndex, spacing) - halfSpacing; + float xy2 = x * x + y * y; + + float height = 0f; + if (xy2 < sphereR2) { + float h = FastMath.sqrt(sphereR2 - xy2); + if (h > hThreshold) { + height = hThreshold - h; + } + } + + int floatIndex = size * rowIndex + columnIndex; + result[floatIndex] = height; + } + } + + return result; + } + + /** + * Initialize the terrain quads during startup. + * + * @param assetManager (not null) + */ + public static void initialize(AssetManager assetManager) { + HeightMap heightMap = loadHeightMap(assetManager); + int terrainDiameter = heightMap.getSize(); + int mapSize = terrainDiameter + 1; // number of samples on a side + float[] heightArray = heightMap.getHeightMap(); + int patchSize = 33; // number of samples on a side + largeQuad = new TerrainQuad( + "large terrain", patchSize, mapSize, heightArray); + + patchSize = 3; + mapSize = patchSize; + smallQuad = new TerrainQuad( + "small terrain", patchSize, mapSize, nineHeights); + + patchSize = 5; + float[] heights25 = new float[patchSize * patchSize]; + for (int i = 0; i < patchSize * patchSize; ++i) { + heights25[i] = (i % 3) / 2f; + } + mapSize = patchSize; + quad5x5 = new TerrainQuad( + "5x5 terrain", patchSize, mapSize, heights25); + + patchSize = 9; + float[] heights81 = new float[patchSize * patchSize]; + for (int i = 0; i < patchSize * patchSize; ++i) { + heights81[i] = (i % 5) / 4f; + } + mapSize = patchSize; + quad9x9 = new TerrainQuad( + "9x9 terrain", patchSize, mapSize, heights81); + + patchSize = 17; + mapSize = patchSize; + float[] heights289 = new float[patchSize * patchSize]; + for (int i = 0; i < patchSize * patchSize; ++i) { + heights289[i] = (i % 3) / 2f; + } + quad17x17 = new TerrainQuad( + "17x17 terrain", patchSize, mapSize, heights289); + + patchSize = 33; + float[] heights1089 = new float[patchSize * patchSize]; + for (int i = 0; i < patchSize * patchSize; ++i) { + heights1089[i] = (i % 5) / 4f; + } + mapSize = patchSize; + quad33x33 = new TerrainQuad( + "33x33 terrain", patchSize, mapSize, heights1089); + + patchSize = 65; + float[] heights4225 = new float[patchSize * patchSize]; + for (int i = 0; i < patchSize * patchSize; ++i) { + heights4225[i] = (i % 3) / 2f; + } + mapSize = patchSize; + quad65x65 = new TerrainQuad( + "65x65 terrain", patchSize, mapSize, heights4225); + } + + /** + * Load a 513x513 height map from a texture asset. + * + * @param assetManager (not null) + * @return a new instance + */ + public static HeightMap loadHeightMap(AssetManager assetManager) { + boolean flipY = false; + TextureKey key = new TextureKey( + "Textures/BumpMapTest/Simple_height.png", flipY); + Texture texture = assetManager.loadTexture(key); + Image image = texture.getImage(); + + float heightScale = 1f; + AbstractHeightMap result = new ImageBasedHeightMap(image, heightScale); + result.load(); + + return result; + } + + /** + * Generate a square height array for a quadratic surface of revolution. + * + * @param size the desired size (in rows or columns, ≥2) + * @return a new array + */ + public static float[] quadraticArray(int size) { + Validate.inRange(size, "size", 2, Integer.MAX_VALUE); + + float halfNm1 = (size - 1) / 2f; + float[] result = new float[size * size]; + for (int rowIndex = 0; rowIndex < size; ++rowIndex) { + float x = -1f + rowIndex / halfNm1; // -1 .. +1 + for (int columnIndex = 0; columnIndex < size; ++columnIndex) { + float y = -1f + columnIndex / halfNm1; // -1 .. +1 + float r = MyMath.hypotenuse(x, y); + float height = -0.4f + (r - 0.8f) * (r - 0.8f); + + int floatIndex = size * rowIndex + columnIndex; + result[floatIndex] = height; + } + } + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/package-info.java index cb399ae54..6c97d3d20 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/terrain/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes that generate terrain used in the MinieExamples subproject. - */ -package jme3utilities.minie.test.terrain; +/* + Copyright (c) 2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes that generate terrain used in the MinieExamples subproject. + */ +package jme3utilities.minie.test.terrain; diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Binocular.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Binocular.java index d37250ac5..4fe6ab02c 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Binocular.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Binocular.java @@ -1,72 +1,72 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.math.Vector3f; - -/** - * An interface to a binocular model composed of physics links. - * - * @author Stephen Gold sgold@sonic.net - */ -public interface Binocular { - /** - * Copy the center-of-vision direction for the model's left eye. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - Vector3f leftEyeLookDirection(Vector3f storeResult); - - /** - * Read the vertex spec for the model's left pupil. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - String leftPupilSpec(); - - /** - * Copy the center-of-vision direction for the model's right eye. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - Vector3f rightEyeLookDirection(Vector3f storeResult); - - /** - * Read the vertex spec for the model's right pupil. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - String rightPupilSpec(); -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.math.Vector3f; + +/** + * An interface to a binocular model composed of physics links. + * + * @author Stephen Gold sgold@sonic.net + */ +public interface Binocular { + /** + * Copy the center-of-vision direction for the model's left eye. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + Vector3f leftEyeLookDirection(Vector3f storeResult); + + /** + * Read the vertex spec for the model's left pupil. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + String leftPupilSpec(); + + /** + * Copy the center-of-vision direction for the model's right eye. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + Vector3f rightEyeLookDirection(Vector3f storeResult); + + /** + * Read the vertex spec for the model's right pupil. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + String rightPupilSpec(); +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Biped.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Biped.java index 714c3cd2e..e5ffd4d4b 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Biped.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Biped.java @@ -1,50 +1,50 @@ -/* - Copyright (c) 2018, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.animation.BoneLink; - -/** - * An interface to a bipedal model composed of physics links. - * - * @author Stephen Gold sgold@sonic.net - */ -public interface Biped { - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - BoneLink getLeftFoot(); - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - BoneLink getRightFoot(); -} +/* + Copyright (c) 2018, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.animation.BoneLink; + +/** + * An interface to a bipedal model composed of physics links. + * + * @author Stephen Gold sgold@sonic.net + */ +public interface Biped { + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + BoneLink getLeftFoot(); + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + BoneLink getRightFoot(); +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/ElephantControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/ElephantControl.java index 71b3cd941..1096bcd1d 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/ElephantControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/ElephantControl.java @@ -1,175 +1,175 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the Elephant model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class ElephantControl - extends DynamicAnimControl - implements Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(ElephantControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new control tuned for the Elephant model. - */ - public ElephantControl() { - super(); - LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.ZYX); - - super.setConfig(torsoName, hull); - - // head - super.link("joint5", hull, - new RangeOfMotion(1f, -1f, 1f, -1f, 0.6f, -0.3f)); - - super.link("Ear_L", hull, - new RangeOfMotion(0.2f, -0.2f, 0.4f, -1f, 0f, 0f)); - super.link("Ear_B1_L", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_M1_L", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_T1_L", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_B2_L", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_M4_L", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_T2_L", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - - super.link("Ear_R", hull, - new RangeOfMotion(0.2f, -0.2f, 0.4f, -1f, 0f, 0f)); - super.link("Ear_B1_R", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_M1_R", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_T1_R", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_B2_R", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_M4_R", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - super.link("Ear_T2_R", hull, - new RangeOfMotion(0.2f, 1f, 1f)); - - // trunk - super.link("joint11", hull, - new RangeOfMotion(1f, -1f, 1f, -1f, 0.5f, -1f)); - super.link("joint12", hull, - new RangeOfMotion(1f, -1f, 1f, -1f, 0.5f, -1f)); - super.link("joint14", hull, - new RangeOfMotion(1f, -1f, 1.5f, -1.5f, 1f, -1.5f)); - - super.link("Tail", hull, - new RangeOfMotion(0.2f, 1f, 0.1f)); - super.link("joint19", hull, - new RangeOfMotion(0.2f, 1f, 0.1f)); - - super.link("Oberschenkel_F_R", hull, - new RangeOfMotion(0f, -0.2f, 0.4f, -0.4f, 0.5f, -0.5f)); - super.link("Knee_F_R", hull, - new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); - super.link("Foot_F_R", hull, - new RangeOfMotion(0f, 0.2f, 0.2f)); - - super.link("Oberschenkel_F_L", hull, - new RangeOfMotion(0f, -0.2f, 0.4f, -0.4f, 0.5f, -0.5f)); - super.link("Knee_F_L", hull, - new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); - super.link("Foot_F_L", hull, - new RangeOfMotion(0f, 0.2f, 0.2f)); - - super.link("Oberschenkel_B_R", hull, - new RangeOfMotion(0f, 0.5f, 0.5f)); - super.link("Knee_B_R", hull, - new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); - super.link("Foot_B_R", hull, - new RangeOfMotion(0f, 0.2f, 0.2f)); - - super.link("Oberschenkel_B_L", hull, - new RangeOfMotion(0f, 0.5f, 0.5f)); - super.link("Knee_B_L", hull, - new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); - super.link("Foot_B_L", hull, - new RangeOfMotion(0f, 0.2f, 0.2f)); - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "1524/Elephant-geom-1"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(1f, 1f, 0f); - result.normalizeLocal(); - - return result; - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the Elephant model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class ElephantControl + extends DynamicAnimControl + implements Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(ElephantControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new control tuned for the Elephant model. + */ + public ElephantControl() { + super(); + LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.ZYX); + + super.setConfig(torsoName, hull); + + // head + super.link("joint5", hull, + new RangeOfMotion(1f, -1f, 1f, -1f, 0.6f, -0.3f)); + + super.link("Ear_L", hull, + new RangeOfMotion(0.2f, -0.2f, 0.4f, -1f, 0f, 0f)); + super.link("Ear_B1_L", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_M1_L", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_T1_L", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_B2_L", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_M4_L", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_T2_L", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + + super.link("Ear_R", hull, + new RangeOfMotion(0.2f, -0.2f, 0.4f, -1f, 0f, 0f)); + super.link("Ear_B1_R", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_M1_R", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_T1_R", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_B2_R", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_M4_R", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + super.link("Ear_T2_R", hull, + new RangeOfMotion(0.2f, 1f, 1f)); + + // trunk + super.link("joint11", hull, + new RangeOfMotion(1f, -1f, 1f, -1f, 0.5f, -1f)); + super.link("joint12", hull, + new RangeOfMotion(1f, -1f, 1f, -1f, 0.5f, -1f)); + super.link("joint14", hull, + new RangeOfMotion(1f, -1f, 1.5f, -1.5f, 1f, -1.5f)); + + super.link("Tail", hull, + new RangeOfMotion(0.2f, 1f, 0.1f)); + super.link("joint19", hull, + new RangeOfMotion(0.2f, 1f, 0.1f)); + + super.link("Oberschenkel_F_R", hull, + new RangeOfMotion(0f, -0.2f, 0.4f, -0.4f, 0.5f, -0.5f)); + super.link("Knee_F_R", hull, + new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); + super.link("Foot_F_R", hull, + new RangeOfMotion(0f, 0.2f, 0.2f)); + + super.link("Oberschenkel_F_L", hull, + new RangeOfMotion(0f, -0.2f, 0.4f, -0.4f, 0.5f, -0.5f)); + super.link("Knee_F_L", hull, + new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); + super.link("Foot_F_L", hull, + new RangeOfMotion(0f, 0.2f, 0.2f)); + + super.link("Oberschenkel_B_R", hull, + new RangeOfMotion(0f, 0.5f, 0.5f)); + super.link("Knee_B_R", hull, + new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); + super.link("Foot_B_R", hull, + new RangeOfMotion(0f, 0.2f, 0.2f)); + + super.link("Oberschenkel_B_L", hull, + new RangeOfMotion(0f, 0.5f, 0.5f)); + super.link("Knee_B_L", hull, + new RangeOfMotion(0.2f, -0.2f, 0.2f, -0.2f, 0f, -0.5f)); + super.link("Foot_B_L", hull, + new RangeOfMotion(0f, 0.2f, 0.2f)); + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "1524/Elephant-geom-1"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(1f, 1f, 0f); + result.normalizeLocal(); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Face.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Face.java index 54376e3c2..17221eabb 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Face.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/Face.java @@ -1,55 +1,55 @@ -/* - Copyright (c) 2019-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.math.Vector3f; - -/** - * Interface to a linked model with a face. - * - * @author Stephen Gold sgold@sonic.net - */ -public interface Face { - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - String faceCenterSpec(); - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - Vector3f faceDirection(Vector3f storeResult); -} +/* + Copyright (c) 2019-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.math.Vector3f; + +/** + * Interface to a linked model with a face. + * + * @author Stephen Gold sgold@sonic.net + */ +public interface Face { + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + String faceCenterSpec(); + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + Vector3f faceDirection(Vector3f storeResult); +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/JaimeControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/JaimeControl.java index c34fa057a..7ddc72ca2 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/JaimeControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/JaimeControl.java @@ -1,239 +1,239 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the Jaime model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class JaimeControl - extends DynamicAnimControl - implements Binocular, Biped, Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(JaimeControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new DynamicAnimControl tuned for the Jaime model. - */ - public JaimeControl() { - super(); - LinkConfig hull = new LinkConfig(0.005f, MassHeuristic.Mass, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.XZY); - LinkConfig hullZxy = new LinkConfig(0.005f, MassHeuristic.Mass, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.ZXY); - - super.setConfig(torsoName, hull); - super.setMainBoneName("pelvis"); - - super.link("spine", hull, - new RangeOfMotion(1f)); - super.link("ribs", hull, - new RangeOfMotion(0.6f, 0.4f, 0.4f)); - super.link("head", hull, - new RangeOfMotion(0.3f, -0.6f, 0.5f, -0.5f, 0.5f, -0.5f)); - super.link("eye.L", hull, - new RangeOfMotion(0.5f, 0f, 0.5f)); - super.link("eye.R", hull, - new RangeOfMotion(0.5f, 0f, 0.5f)); - - super.link("tail.001", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - super.link("tail.002", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - super.link("tail.003", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - super.link("tail.004", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - super.link("tail.005", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - super.link("tail.007", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - super.link("tail.009", hull, - new RangeOfMotion(0.5f, 0.2f, 0.5f)); - - super.link("shoulder.R", hull, - new RangeOfMotion(0.8f, -1.6f, 0f, 0f, 0.3f, -0.6f)); - super.link("upper_arm.R", hullZxy, - new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1.6f, -1.8f)); - super.link("forearm.R", hull, - new RangeOfMotion(0f, -2f, 1f, -1f, 0f, 0f)); - super.link("hand.R", hull, - new RangeOfMotion(0.3f, -0.8f, 0f, 0f, 0.2f, -0.2f)); - - super.link("shoulder.L", hull, - new RangeOfMotion(1.6f, -0.8f, 0f, 0f, 0.6f, -0.3f)); - super.link("upper_arm.L", hullZxy, - new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1.6f, -1.8f)); - super.link("forearm.L", hull, - new RangeOfMotion(0f, -2f, 1f, -1f, 0f, 0f)); - super.link("hand.L", hull, - new RangeOfMotion(0.8f, -0.3f, 0f, 0f, 0.2f, -0.2f)); - - super.link("thigh.R", hull, - new RangeOfMotion(1f, -0.4f, 0.4f, -0.4f, 0.5f, -0.5f)); - super.link("shin.R", hullZxy, - new RangeOfMotion(0f, 0f, 0f, 0f, 2f, 0f)); - super.link("foot.R", hull, - new RangeOfMotion(0.6f, 0.2f, 0f)); - - super.link("thigh.L", hull, - new RangeOfMotion(1f, -0.4f, 0.4f, -0.4f, 0.5f, -0.5f)); - super.link("shin.L", hullZxy, - new RangeOfMotion(0f, 0f, 0f, 0f, 0f, -2f)); - super.link("foot.L", hull, - new RangeOfMotion(0.6f, 0.2f, 0f)); - } - // ************************************************************************* - // Binocular methods - - /** - * Copy the center-of-vision direction for the model's left eye. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f leftEyeLookDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 1f, 0f); - return result; - } - - /** - * Read the vertex spec for the model's left pupil. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String leftPupilSpec() { - return "2949/JaimeGeom-geom-1"; - } - - /** - * Copy the center-of-vision direction for the model's right eye. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f rightEyeLookDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 1f, 0f); - return result; - } - - /** - * Read the vertex spec for the model's right pupil. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String rightPupilSpec() { - return "3046/JaimeGeom-geom-1"; - } - // ************************************************************************* - // Biped methods - - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getLeftFoot() { - BoneLink result = findBoneLink("foot.L"); - return result; - } - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getRightFoot() { - BoneLink result = findBoneLink("foot.R"); - return result; - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "122/JaimeGeom-geom-1"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 0f, 1f); - return result; - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the Jaime model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class JaimeControl + extends DynamicAnimControl + implements Binocular, Biped, Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(JaimeControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new DynamicAnimControl tuned for the Jaime model. + */ + public JaimeControl() { + super(); + LinkConfig hull = new LinkConfig(0.005f, MassHeuristic.Mass, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.XZY); + LinkConfig hullZxy = new LinkConfig(0.005f, MassHeuristic.Mass, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.ZXY); + + super.setConfig(torsoName, hull); + super.setMainBoneName("pelvis"); + + super.link("spine", hull, + new RangeOfMotion(1f)); + super.link("ribs", hull, + new RangeOfMotion(0.6f, 0.4f, 0.4f)); + super.link("head", hull, + new RangeOfMotion(0.3f, -0.6f, 0.5f, -0.5f, 0.5f, -0.5f)); + super.link("eye.L", hull, + new RangeOfMotion(0.5f, 0f, 0.5f)); + super.link("eye.R", hull, + new RangeOfMotion(0.5f, 0f, 0.5f)); + + super.link("tail.001", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + super.link("tail.002", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + super.link("tail.003", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + super.link("tail.004", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + super.link("tail.005", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + super.link("tail.007", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + super.link("tail.009", hull, + new RangeOfMotion(0.5f, 0.2f, 0.5f)); + + super.link("shoulder.R", hull, + new RangeOfMotion(0.8f, -1.6f, 0f, 0f, 0.3f, -0.6f)); + super.link("upper_arm.R", hullZxy, + new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1.6f, -1.8f)); + super.link("forearm.R", hull, + new RangeOfMotion(0f, -2f, 1f, -1f, 0f, 0f)); + super.link("hand.R", hull, + new RangeOfMotion(0.3f, -0.8f, 0f, 0f, 0.2f, -0.2f)); + + super.link("shoulder.L", hull, + new RangeOfMotion(1.6f, -0.8f, 0f, 0f, 0.6f, -0.3f)); + super.link("upper_arm.L", hullZxy, + new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1.6f, -1.8f)); + super.link("forearm.L", hull, + new RangeOfMotion(0f, -2f, 1f, -1f, 0f, 0f)); + super.link("hand.L", hull, + new RangeOfMotion(0.8f, -0.3f, 0f, 0f, 0.2f, -0.2f)); + + super.link("thigh.R", hull, + new RangeOfMotion(1f, -0.4f, 0.4f, -0.4f, 0.5f, -0.5f)); + super.link("shin.R", hullZxy, + new RangeOfMotion(0f, 0f, 0f, 0f, 2f, 0f)); + super.link("foot.R", hull, + new RangeOfMotion(0.6f, 0.2f, 0f)); + + super.link("thigh.L", hull, + new RangeOfMotion(1f, -0.4f, 0.4f, -0.4f, 0.5f, -0.5f)); + super.link("shin.L", hullZxy, + new RangeOfMotion(0f, 0f, 0f, 0f, 0f, -2f)); + super.link("foot.L", hull, + new RangeOfMotion(0.6f, 0.2f, 0f)); + } + // ************************************************************************* + // Binocular methods + + /** + * Copy the center-of-vision direction for the model's left eye. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f leftEyeLookDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 1f, 0f); + return result; + } + + /** + * Read the vertex spec for the model's left pupil. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String leftPupilSpec() { + return "2949/JaimeGeom-geom-1"; + } + + /** + * Copy the center-of-vision direction for the model's right eye. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f rightEyeLookDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 1f, 0f); + return result; + } + + /** + * Read the vertex spec for the model's right pupil. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String rightPupilSpec() { + return "3046/JaimeGeom-geom-1"; + } + // ************************************************************************* + // Biped methods + + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getLeftFoot() { + BoneLink result = findBoneLink("foot.L"); + return result; + } + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getRightFoot() { + BoneLink result = findBoneLink("foot.R"); + return result; + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "122/JaimeGeom-geom-1"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 0f, 1f); + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/MhGameControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/MhGameControl.java index 19b92fa25..b70ba5de5 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/MhGameControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/MhGameControl.java @@ -1,168 +1,168 @@ -/* - Copyright (c) 2018-2023 Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the MhGame model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class MhGameControl - extends DynamicAnimControl - implements Biped, Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(MhGameControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new control tuned for the MhGame model. - */ - public MhGameControl() { - super(); - LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.XZY); - - // Generate FourSphere shapes for links with > 1,000 mesh vertices. - LinkConfig simplified = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.FourSphere, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.XZY); - - super.setConfig(torsoName, hull); - super.setMainBoneName("pelvis"); - - super.link("spine_03", simplified, - new RangeOfMotion(0.8f, 0.5f, 0.5f)); // 2,047 vertices - super.link("neck_01", hull, - new RangeOfMotion(0.5f, 0.5f, 0.3f)); - super.link("head", simplified, - new RangeOfMotion(0.4f, 0.6f, 0.2f)); // 4,288 vertices - - super.link("clavicle_r", hull, - new RangeOfMotion(0.4f, 0f, 0.2f)); - super.link("upperarm_r", hull, - new RangeOfMotion(0.2f, -1f, 0.8f, -0.8f, 1f, -1f)); - super.link("lowerarm_r", hull, - new RangeOfMotion(0.8f, -1.3f, 0f, 0f, 0f, 0f)); - super.link("hand_r", simplified, - new RangeOfMotion(0.7f, 0f, 0.2f)); // 1,596 vertices - - super.link("clavicle_l", hull, - new RangeOfMotion(0.4f, 0f, 0.2f)); - super.link("upperarm_l", hull, - new RangeOfMotion(0.2f, -1f, 0.8f, -0.8f, 1f, -1f)); - super.link("lowerarm_l", hull, - new RangeOfMotion(0.8f, -1.3f, 0f, 0f, 0f, 0f)); - super.link("hand_l", simplified, - new RangeOfMotion(0.7f, 0f, 0.2f)); // 1,596 vertices - - super.link("thigh_r", hull, - new RangeOfMotion(1f, -0.2f, 0.1f, -0.2f, 0.1f, -0.2f)); - super.link("calf_r", hull, - new RangeOfMotion(0f, -2.2f, 0f, 0f, 0f, 0f)); - super.link("foot_r", simplified, - new RangeOfMotion(1f, 0.3f, 0.5f)); // 1,090 vertices - - super.link("thigh_l", hull, - new RangeOfMotion(1f, -0.2f, 0.2f, -0.1f, 0.2f, -0.1f)); - super.link("calf_l", hull, - new RangeOfMotion(0f, -2.2f, 0f, 0f, 0f, 0f)); - super.link("foot_l", simplified, - new RangeOfMotion(1f, 0.3f, 0.5f)); // 1,090 vertices - } - // ************************************************************************* - // Biped methods - - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getLeftFoot() { - BoneLink result = findBoneLink("foot_l"); - return result; - } - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getRightFoot() { - BoneLink result = findBoneLink("foot_r"); - return result; - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "9313/male_generic"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 0f, 1f); - return result; - } -} +/* + Copyright (c) 2018-2023 Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the MhGame model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class MhGameControl + extends DynamicAnimControl + implements Biped, Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(MhGameControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new control tuned for the MhGame model. + */ + public MhGameControl() { + super(); + LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.XZY); + + // Generate FourSphere shapes for links with > 1,000 mesh vertices. + LinkConfig simplified = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.FourSphere, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.XZY); + + super.setConfig(torsoName, hull); + super.setMainBoneName("pelvis"); + + super.link("spine_03", simplified, + new RangeOfMotion(0.8f, 0.5f, 0.5f)); // 2,047 vertices + super.link("neck_01", hull, + new RangeOfMotion(0.5f, 0.5f, 0.3f)); + super.link("head", simplified, + new RangeOfMotion(0.4f, 0.6f, 0.2f)); // 4,288 vertices + + super.link("clavicle_r", hull, + new RangeOfMotion(0.4f, 0f, 0.2f)); + super.link("upperarm_r", hull, + new RangeOfMotion(0.2f, -1f, 0.8f, -0.8f, 1f, -1f)); + super.link("lowerarm_r", hull, + new RangeOfMotion(0.8f, -1.3f, 0f, 0f, 0f, 0f)); + super.link("hand_r", simplified, + new RangeOfMotion(0.7f, 0f, 0.2f)); // 1,596 vertices + + super.link("clavicle_l", hull, + new RangeOfMotion(0.4f, 0f, 0.2f)); + super.link("upperarm_l", hull, + new RangeOfMotion(0.2f, -1f, 0.8f, -0.8f, 1f, -1f)); + super.link("lowerarm_l", hull, + new RangeOfMotion(0.8f, -1.3f, 0f, 0f, 0f, 0f)); + super.link("hand_l", simplified, + new RangeOfMotion(0.7f, 0f, 0.2f)); // 1,596 vertices + + super.link("thigh_r", hull, + new RangeOfMotion(1f, -0.2f, 0.1f, -0.2f, 0.1f, -0.2f)); + super.link("calf_r", hull, + new RangeOfMotion(0f, -2.2f, 0f, 0f, 0f, 0f)); + super.link("foot_r", simplified, + new RangeOfMotion(1f, 0.3f, 0.5f)); // 1,090 vertices + + super.link("thigh_l", hull, + new RangeOfMotion(1f, -0.2f, 0.2f, -0.1f, 0.2f, -0.1f)); + super.link("calf_l", hull, + new RangeOfMotion(0f, -2.2f, 0f, 0f, 0f, 0f)); + super.link("foot_l", simplified, + new RangeOfMotion(1f, 0.3f, 0.5f)); // 1,090 vertices + } + // ************************************************************************* + // Biped methods + + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getLeftFoot() { + BoneLink result = findBoneLink("foot_l"); + return result; + } + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getRightFoot() { + BoneLink result = findBoneLink("foot_r"); + return result; + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "9313/male_generic"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 0f, 1f); + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/NinjaControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/NinjaControl.java index 47c6c41c7..1575c19f6 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/NinjaControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/NinjaControl.java @@ -1,166 +1,166 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the Ninja model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class NinjaControl - extends DynamicAnimControl - implements Biped, Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(NinjaControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new control tuned for the Ninja model. - */ - public NinjaControl() { - super(); - LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.XZY); - - super.setConfig(torsoName, hull); - super.setMainBoneName("Joint2"); - - // chest - super.link("Joint4", hull, - new RangeOfMotion(1f, -0.5f, 0.8f, -0.8f, 0.3f, -0.3f)); - super.link("Joint6", hull, - new RangeOfMotion(0.5f, 0.8f, 0f)); - - // head - super.link("Joint7", hull, - new RangeOfMotion(0.8f, -0.5f, 1f, -1f, 0.8f, -0.8f)); - - // right arm and katana - super.link("Joint9", hull, - new RangeOfMotion(0.3f, -1f, 1f, -1f, 0.3f, -1f)); - super.link("Joint11", hull, - new RangeOfMotion(0f, -1.4f, 0.8f, -0.8f, 0f, -0f)); - super.link("Joint12", hull, - new RangeOfMotion(0.5f, 1f, 0f)); - - // left arm - super.link("Joint14", hull, - new RangeOfMotion(0.3f, -1f, 1f, -1f, 1f, -0.3f)); - super.link("Joint16", hull, - new RangeOfMotion(0f, -1.4f, 0.8f, -0.8f, 0f, -0f)); - super.link("Joint17", hull, - new RangeOfMotion(0.5f, 1f, 0f)); - - // right leg - super.link("Joint18", hull, - new RangeOfMotion(0.3f, -1f, 0.3f, -0.3f, 0.2f, -0.5f)); - super.link("Joint19", hull, - new RangeOfMotion(1.2f, 0f, 0.2f, -0.2f, 0f, 0f)); - super.link("Joint21", hull, - new RangeOfMotion(0.5f, 0.2f, 0.1f)); - - // left leg - super.link("Joint23", hull, - new RangeOfMotion(0.3f, -1f, 0.3f, -0.3f, 0.5f, -0.2f)); - super.link("Joint24", hull, - new RangeOfMotion(1.2f, 0f, 0.2f, -0.2f, 0f, 0f)); - super.link("Joint26", hull, - new RangeOfMotion(0.5f, 0.2f, 0.1f)); - } - // ************************************************************************* - // Biped methods - - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getLeftFoot() { - BoneLink result = findBoneLink("Joint26"); - return result; - } - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getRightFoot() { - BoneLink result = findBoneLink("Joint21"); - return result; - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "277/Ninja-geom-1"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 0f, -1f); - return result; - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the Ninja model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class NinjaControl + extends DynamicAnimControl + implements Biped, Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(NinjaControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new control tuned for the Ninja model. + */ + public NinjaControl() { + super(); + LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.XZY); + + super.setConfig(torsoName, hull); + super.setMainBoneName("Joint2"); + + // chest + super.link("Joint4", hull, + new RangeOfMotion(1f, -0.5f, 0.8f, -0.8f, 0.3f, -0.3f)); + super.link("Joint6", hull, + new RangeOfMotion(0.5f, 0.8f, 0f)); + + // head + super.link("Joint7", hull, + new RangeOfMotion(0.8f, -0.5f, 1f, -1f, 0.8f, -0.8f)); + + // right arm and katana + super.link("Joint9", hull, + new RangeOfMotion(0.3f, -1f, 1f, -1f, 0.3f, -1f)); + super.link("Joint11", hull, + new RangeOfMotion(0f, -1.4f, 0.8f, -0.8f, 0f, -0f)); + super.link("Joint12", hull, + new RangeOfMotion(0.5f, 1f, 0f)); + + // left arm + super.link("Joint14", hull, + new RangeOfMotion(0.3f, -1f, 1f, -1f, 1f, -0.3f)); + super.link("Joint16", hull, + new RangeOfMotion(0f, -1.4f, 0.8f, -0.8f, 0f, -0f)); + super.link("Joint17", hull, + new RangeOfMotion(0.5f, 1f, 0f)); + + // right leg + super.link("Joint18", hull, + new RangeOfMotion(0.3f, -1f, 0.3f, -0.3f, 0.2f, -0.5f)); + super.link("Joint19", hull, + new RangeOfMotion(1.2f, 0f, 0.2f, -0.2f, 0f, 0f)); + super.link("Joint21", hull, + new RangeOfMotion(0.5f, 0.2f, 0.1f)); + + // left leg + super.link("Joint23", hull, + new RangeOfMotion(0.3f, -1f, 0.3f, -0.3f, 0.5f, -0.2f)); + super.link("Joint24", hull, + new RangeOfMotion(1.2f, 0f, 0.2f, -0.2f, 0f, 0f)); + super.link("Joint26", hull, + new RangeOfMotion(0.5f, 0.2f, 0.1f)); + } + // ************************************************************************* + // Biped methods + + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getLeftFoot() { + BoneLink result = findBoneLink("Joint26"); + return result; + } + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getRightFoot() { + BoneLink result = findBoneLink("Joint21"); + return result; + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "277/Ninja-geom-1"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 0f, -1f); + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/OtoControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/OtoControl.java index 6d47d301e..171981d45 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/OtoControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/OtoControl.java @@ -1,156 +1,156 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the Oto model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class OtoControl - extends DynamicAnimControl - implements Biped, Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(OtoControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new control tuned for the Oto model. - */ - public OtoControl() { - super(); - LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.ZXY); - - super.setConfig(torsoName, hull); - - super.link("spinehigh", hull, - new RangeOfMotion(0.3f, -0.3f, 1f, -1f, 0.5f, -1f)); - super.link("head", hull, - new RangeOfMotion(0f, 1f, 0.5f)); - - super.link("hip.left", hull, - new RangeOfMotion(0.2f, -1f, 0f, 0f, 1f, -0.2f)); - super.link("leg.left", hull, - new RangeOfMotion(0f, 0f, 0f, 0f, 0f, -2f)); - super.link("foot.left", hull, - new RangeOfMotion(0.5f, -0.5f, 0.3f, 0f, 0.5f, 0.5f)); - - super.link("hip.right", hull, - new RangeOfMotion(1f, -0.2f, 0f, 0f, 1f, -0.2f)); - super.link("leg.right", hull, - new RangeOfMotion(0f, 0f, 0f, 0f, 0f, -2f)); - super.link("foot.right", hull, - new RangeOfMotion(0.5f, -0.5f, 0f, -0.3f, 0.5f, 0.5f)); - - super.link("uparm.left", hull, - new RangeOfMotion(1f, -1f, 1f, -1f, 0.2f, -1f)); - super.link("arm.left", hull, - new RangeOfMotion(0f, 0f, 0.5f, -0.5f, 1.2f, 0f)); - super.link("hand.left", hull, - new RangeOfMotion(1f, -0.5f, 0f, 0f, 0.1f, -0.1f)); - - super.link("uparm.right", hull, - new RangeOfMotion(1f, -1f, 1f, -1f, 0.2f, -1f)); - super.link("arm.right", hull, - new RangeOfMotion(0f, 0f, 0.5f, -0.5f, 0f, -1.2f)); - super.link("hand.right", hull, - new RangeOfMotion(0.5f, -1f, 0f, 0f, 0.1f, -0.1f)); - } - // ************************************************************************* - // Biped methods - - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getLeftFoot() { - BoneLink result = findBoneLink("foot.left"); - return result; - } - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getRightFoot() { - BoneLink result = findBoneLink("foot.right"); - return result; - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "161/Oto-geom-1"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(-1f, 0f, 0f); - return result; - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the Oto model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class OtoControl + extends DynamicAnimControl + implements Biped, Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(OtoControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new control tuned for the Oto model. + */ + public OtoControl() { + super(); + LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.ZXY); + + super.setConfig(torsoName, hull); + + super.link("spinehigh", hull, + new RangeOfMotion(0.3f, -0.3f, 1f, -1f, 0.5f, -1f)); + super.link("head", hull, + new RangeOfMotion(0f, 1f, 0.5f)); + + super.link("hip.left", hull, + new RangeOfMotion(0.2f, -1f, 0f, 0f, 1f, -0.2f)); + super.link("leg.left", hull, + new RangeOfMotion(0f, 0f, 0f, 0f, 0f, -2f)); + super.link("foot.left", hull, + new RangeOfMotion(0.5f, -0.5f, 0.3f, 0f, 0.5f, 0.5f)); + + super.link("hip.right", hull, + new RangeOfMotion(1f, -0.2f, 0f, 0f, 1f, -0.2f)); + super.link("leg.right", hull, + new RangeOfMotion(0f, 0f, 0f, 0f, 0f, -2f)); + super.link("foot.right", hull, + new RangeOfMotion(0.5f, -0.5f, 0f, -0.3f, 0.5f, 0.5f)); + + super.link("uparm.left", hull, + new RangeOfMotion(1f, -1f, 1f, -1f, 0.2f, -1f)); + super.link("arm.left", hull, + new RangeOfMotion(0f, 0f, 0.5f, -0.5f, 1.2f, 0f)); + super.link("hand.left", hull, + new RangeOfMotion(1f, -0.5f, 0f, 0f, 0.1f, -0.1f)); + + super.link("uparm.right", hull, + new RangeOfMotion(1f, -1f, 1f, -1f, 0.2f, -1f)); + super.link("arm.right", hull, + new RangeOfMotion(0f, 0f, 0.5f, -0.5f, 0f, -1.2f)); + super.link("hand.right", hull, + new RangeOfMotion(0.5f, -1f, 0f, 0f, 0.1f, -0.1f)); + } + // ************************************************************************* + // Biped methods + + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getLeftFoot() { + BoneLink result = findBoneLink("foot.left"); + return result; + } + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getRightFoot() { + BoneLink result = findBoneLink("foot.right"); + return result; + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "161/Oto-geom-1"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(-1f, 0f, 0f); + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/PuppetControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/PuppetControl.java index 6af418a84..36bf7010d 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/PuppetControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/PuppetControl.java @@ -1,171 +1,171 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the Puppet model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class PuppetControl - extends DynamicAnimControl - implements Biped, Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(PuppetControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new control tuned for the Puppet model. - */ - public PuppetControl() { - super(); - LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.XYZ); - - super.setConfig(torsoName, hull); - super.setMainBoneName("hips"); - - super.link("spine", hull, - new RangeOfMotion(0f, -1f, 0.7f, -0.7f, 0.7f, -0.7f)); - super.link("chest", hull, - new RangeOfMotion(0.2f, 0f, 0.1f, -0.1f, 0.3f, -0.3f)); - super.link("upper_chest", hull, - new RangeOfMotion(0.1f, 0f, 0.1f, -0.1f, 0.2f, -0.2f)); - super.link("neck", hull, - new RangeOfMotion(0.2f, -0.5f, 0.5f, -0.5f, 0.5f, -0.5f)); - super.link("head", hull, - new RangeOfMotion(0.5f)); - - super.link("shoulder.R", hull, - new RangeOfMotion(0.1f, 0.2f, 0f)); - super.link("upper_arm.1.R", hull, - new RangeOfMotion(1.5f, -0.5f, 1.5f, -0.5f, 1f, -1f)); - super.link("forearm.1.R", hull, - new RangeOfMotion(0f, 0f, 1.5f, 0f, 1f, -1f)); - super.link("hand.R", hull, - new RangeOfMotion(0.8f, 0.1f, 0f)); - - super.link("shoulder.L", hull, - new RangeOfMotion(0.1f, 0.2f, 0f)); - super.link("upper_arm.1.L", hull, - new RangeOfMotion(0.5f, -1.5f, 1.5f, -0.5f, 1f, -1f)); - super.link("forearm.1.L", hull, - new RangeOfMotion(0f, 0f, 1.5f, 0f, 1f, -1f)); - super.link("hand.L", hull, - new RangeOfMotion(0.8f, 0.1f, 0f)); - - super.link("thigh.R", hull, - new RangeOfMotion(1f, -0.5f, 0.3f, -0.8f, 0.2f, -0.2f)); - super.link("shin.R", hull, - new RangeOfMotion(0f, -1.6f, 0f, 0f, 0f, 0f)); - super.link("foot.R", hull, - new RangeOfMotion(0.5f, 0.4f, 0.2f)); - super.link("toe.R", hull, - new RangeOfMotion(0.5f, 0f, 0f)); - - super.link("thigh.L", hull, - new RangeOfMotion(1f, -0.5f, 0.8f, -0.3f, 0.2f, -0.2f)); - super.link("shin.L", hull, - new RangeOfMotion(0f, -1.6f, 0f, 0f, 0f, 0f)); - super.link("foot.L", hull, - new RangeOfMotion(0.5f, 0.4f, 0.2f)); - super.link("toe.L", hull, - new RangeOfMotion(0.5f, 0f, 0f)); - } - // ************************************************************************* - // Biped methods - - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getLeftFoot() { - BoneLink result = findBoneLink("foot.L"); - return result; - } - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getRightFoot() { - BoneLink result = findBoneLink("foot.R"); - return result; - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "140/Mesh.011_0"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 1f, 0f); - return result; - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the Puppet model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class PuppetControl + extends DynamicAnimControl + implements Biped, Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(PuppetControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new control tuned for the Puppet model. + */ + public PuppetControl() { + super(); + LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.XYZ); + + super.setConfig(torsoName, hull); + super.setMainBoneName("hips"); + + super.link("spine", hull, + new RangeOfMotion(0f, -1f, 0.7f, -0.7f, 0.7f, -0.7f)); + super.link("chest", hull, + new RangeOfMotion(0.2f, 0f, 0.1f, -0.1f, 0.3f, -0.3f)); + super.link("upper_chest", hull, + new RangeOfMotion(0.1f, 0f, 0.1f, -0.1f, 0.2f, -0.2f)); + super.link("neck", hull, + new RangeOfMotion(0.2f, -0.5f, 0.5f, -0.5f, 0.5f, -0.5f)); + super.link("head", hull, + new RangeOfMotion(0.5f)); + + super.link("shoulder.R", hull, + new RangeOfMotion(0.1f, 0.2f, 0f)); + super.link("upper_arm.1.R", hull, + new RangeOfMotion(1.5f, -0.5f, 1.5f, -0.5f, 1f, -1f)); + super.link("forearm.1.R", hull, + new RangeOfMotion(0f, 0f, 1.5f, 0f, 1f, -1f)); + super.link("hand.R", hull, + new RangeOfMotion(0.8f, 0.1f, 0f)); + + super.link("shoulder.L", hull, + new RangeOfMotion(0.1f, 0.2f, 0f)); + super.link("upper_arm.1.L", hull, + new RangeOfMotion(0.5f, -1.5f, 1.5f, -0.5f, 1f, -1f)); + super.link("forearm.1.L", hull, + new RangeOfMotion(0f, 0f, 1.5f, 0f, 1f, -1f)); + super.link("hand.L", hull, + new RangeOfMotion(0.8f, 0.1f, 0f)); + + super.link("thigh.R", hull, + new RangeOfMotion(1f, -0.5f, 0.3f, -0.8f, 0.2f, -0.2f)); + super.link("shin.R", hull, + new RangeOfMotion(0f, -1.6f, 0f, 0f, 0f, 0f)); + super.link("foot.R", hull, + new RangeOfMotion(0.5f, 0.4f, 0.2f)); + super.link("toe.R", hull, + new RangeOfMotion(0.5f, 0f, 0f)); + + super.link("thigh.L", hull, + new RangeOfMotion(1f, -0.5f, 0.8f, -0.3f, 0.2f, -0.2f)); + super.link("shin.L", hull, + new RangeOfMotion(0f, -1.6f, 0f, 0f, 0f, 0f)); + super.link("foot.L", hull, + new RangeOfMotion(0.5f, 0.4f, 0.2f)); + super.link("toe.L", hull, + new RangeOfMotion(0.5f, 0f, 0f)); + } + // ************************************************************************* + // Biped methods + + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getLeftFoot() { + BoneLink result = findBoneLink("foot.L"); + return result; + } + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getRightFoot() { + BoneLink result = findBoneLink("foot.R"); + return result; + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "140/Mesh.011_0"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 1f, 0f); + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/SinbadControl.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/SinbadControl.java index cce8261c0..c1e2f4953 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/SinbadControl.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/SinbadControl.java @@ -1,222 +1,222 @@ -/* - Copyright (c) 2018-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.test.tunings; - -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.animation.BoneLink; -import com.jme3.bullet.animation.CenterHeuristic; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.MassHeuristic; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.animation.ShapeHeuristic; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; - -/** - * A DynamicAnimControl configured specifically for the Sinbad model. - * - * @author Stephen Gold sgold@sonic.net - */ -public class SinbadControl - extends DynamicAnimControl - implements Binocular, Biped, Face { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger4 - = Logger.getLogger(SinbadControl.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a new control tuned for the Sinbad model. - */ - public SinbadControl() { - super(); - LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, - ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), - CenterHeuristic.Mean, RotationOrder.XZY); - - super.setConfig(torsoName, hull); - - super.link("Waist", hull, - new RangeOfMotion(1f, -0.4f, 0.8f, -0.8f, 0.4f, -0.4f)); - super.link("Chest", hull, - new RangeOfMotion(0.4f, 0f, 0.4f)); - super.link("Neck", hull, - new RangeOfMotion(0.5f, 1f, 0.7f)); - super.link("Eye.L", hull, - new RangeOfMotion(0.3f, -1.2f, 0f, 0f, 0.1f, -0.4f)); - super.link("Eye.R", hull, - new RangeOfMotion(1.2f, -0.3f, 0f, 0f, 0.1f, -0.4f)); - - super.link("Clavicle.R", hull, - new RangeOfMotion(0.3f, -0.6f, 0f, 0f, 0.4f, -0.4f)); - super.link("Humerus.R", hull, - new RangeOfMotion(1.6f, -0.8f, 1f, -1f, 1.6f, -1f)); - super.link("Ulna.R", hull, - new RangeOfMotion(0f, 0f, 1f, -1f, 0f, -2f)); - super.link("Hand.R", hull, - new RangeOfMotion(0.8f, 0f, 0.2f)); - - super.link("Clavicle.L", hull, - new RangeOfMotion(0.6f, -0.3f, 0f, 0f, 0.4f, -0.4f)); - super.link("Humerus.L", hull, - new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1f, -1.6f)); - super.link("Ulna.L", hull, - new RangeOfMotion(0f, 0f, 1f, -1f, 2f, 0f)); - super.link("Hand.L", hull, - new RangeOfMotion(0.8f, 0f, 0.2f)); - - super.link("Thigh.R", hull, - new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 1f, -0.5f)); - super.link("Calf.R", hull, - new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f)); - super.link("Foot.R", hull, - new RangeOfMotion(0.3f, 0.5f, 0f)); - - super.link("Thigh.L", hull, - new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 0.5f, -1f)); - super.link("Calf.L", hull, - new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f)); - super.link("Foot.L", hull, - new RangeOfMotion(0.3f, 0.5f, 0f)); - } - // ************************************************************************* - // Binocular methods - - /** - * Copy the center-of-vision direction for the model's left eye. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f leftEyeLookDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 1f, 0f); - return result; - } - - /** - * Read the vertex spec for the model's left pupil. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String leftPupilSpec() { - return "157/Sinbad-geom-1"; - } - - /** - * Copy the center-of-vision direction for the model's right eye. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f rightEyeLookDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 1f, 0f); - return result; - } - - /** - * Read the vertex spec for the model's right pupil. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String rightPupilSpec() { - return "12/Sinbad-geom-1"; - } - // ************************************************************************* - // Biped methods - - /** - * Access the BoneLink that manages the model's left foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getLeftFoot() { - BoneLink result = findBoneLink("Foot.L"); - return result; - } - - /** - * Access the BoneLink that manages the model's right foot. - * - * @return the pre-existing instance (not null) - */ - @Override - public BoneLink getRightFoot() { - BoneLink result = findBoneLink("Foot.R"); - return result; - } - // ************************************************************************* - // Face methods - - /** - * Read the vertex spec for the center of the model's face. This is - * typically on the bridge of the nose, halfway between the pupils. - * - * @return the vertex specification (not null, not empty) - * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( - * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) - */ - @Override - public String faceCenterSpec() { - return "1844/Sinbad-geom-2"; - } - - /** - * Copy the direction the model's head is facing. - * - * @param storeResult storage for the result (modified if not null) - * @return a direction vector (unit vector in the physics link's local - * coordinates, either storeResult or a new vector) - */ - @Override - public Vector3f faceDirection(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - result.set(0f, 1f, -3f); - result.normalizeLocal(); - - return result; - } -} +/* + Copyright (c) 2018-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.test.tunings; + +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.animation.BoneLink; +import com.jme3.bullet.animation.CenterHeuristic; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.MassHeuristic; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.animation.ShapeHeuristic; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; + +/** + * A DynamicAnimControl configured specifically for the Sinbad model. + * + * @author Stephen Gold sgold@sonic.net + */ +public class SinbadControl + extends DynamicAnimControl + implements Binocular, Biped, Face { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger4 + = Logger.getLogger(SinbadControl.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a new control tuned for the Sinbad model. + */ + public SinbadControl() { + super(); + LinkConfig hull = new LinkConfig(1f, MassHeuristic.Density, + ShapeHeuristic.VertexHull, new Vector3f(1f, 1f, 1f), + CenterHeuristic.Mean, RotationOrder.XZY); + + super.setConfig(torsoName, hull); + + super.link("Waist", hull, + new RangeOfMotion(1f, -0.4f, 0.8f, -0.8f, 0.4f, -0.4f)); + super.link("Chest", hull, + new RangeOfMotion(0.4f, 0f, 0.4f)); + super.link("Neck", hull, + new RangeOfMotion(0.5f, 1f, 0.7f)); + super.link("Eye.L", hull, + new RangeOfMotion(0.3f, -1.2f, 0f, 0f, 0.1f, -0.4f)); + super.link("Eye.R", hull, + new RangeOfMotion(1.2f, -0.3f, 0f, 0f, 0.1f, -0.4f)); + + super.link("Clavicle.R", hull, + new RangeOfMotion(0.3f, -0.6f, 0f, 0f, 0.4f, -0.4f)); + super.link("Humerus.R", hull, + new RangeOfMotion(1.6f, -0.8f, 1f, -1f, 1.6f, -1f)); + super.link("Ulna.R", hull, + new RangeOfMotion(0f, 0f, 1f, -1f, 0f, -2f)); + super.link("Hand.R", hull, + new RangeOfMotion(0.8f, 0f, 0.2f)); + + super.link("Clavicle.L", hull, + new RangeOfMotion(0.6f, -0.3f, 0f, 0f, 0.4f, -0.4f)); + super.link("Humerus.L", hull, + new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1f, -1.6f)); + super.link("Ulna.L", hull, + new RangeOfMotion(0f, 0f, 1f, -1f, 2f, 0f)); + super.link("Hand.L", hull, + new RangeOfMotion(0.8f, 0f, 0.2f)); + + super.link("Thigh.R", hull, + new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 1f, -0.5f)); + super.link("Calf.R", hull, + new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f)); + super.link("Foot.R", hull, + new RangeOfMotion(0.3f, 0.5f, 0f)); + + super.link("Thigh.L", hull, + new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 0.5f, -1f)); + super.link("Calf.L", hull, + new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f)); + super.link("Foot.L", hull, + new RangeOfMotion(0.3f, 0.5f, 0f)); + } + // ************************************************************************* + // Binocular methods + + /** + * Copy the center-of-vision direction for the model's left eye. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f leftEyeLookDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 1f, 0f); + return result; + } + + /** + * Read the vertex spec for the model's left pupil. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String leftPupilSpec() { + return "157/Sinbad-geom-1"; + } + + /** + * Copy the center-of-vision direction for the model's right eye. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f rightEyeLookDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 1f, 0f); + return result; + } + + /** + * Read the vertex spec for the model's right pupil. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String rightPupilSpec() { + return "12/Sinbad-geom-1"; + } + // ************************************************************************* + // Biped methods + + /** + * Access the BoneLink that manages the model's left foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getLeftFoot() { + BoneLink result = findBoneLink("Foot.L"); + return result; + } + + /** + * Access the BoneLink that manages the model's right foot. + * + * @return the pre-existing instance (not null) + */ + @Override + public BoneLink getRightFoot() { + BoneLink result = findBoneLink("Foot.R"); + return result; + } + // ************************************************************************* + // Face methods + + /** + * Read the vertex spec for the center of the model's face. This is + * typically on the bridge of the nose, halfway between the pupils. + * + * @return the vertex specification (not null, not empty) + * @see com.jme3.bullet.animation.DynamicAnimControl#findManagerForVertex( + * java.lang.String, com.jme3.math.Vector3f, com.jme3.math.Vector3f) + */ + @Override + public String faceCenterSpec() { + return "1844/Sinbad-geom-2"; + } + + /** + * Copy the direction the model's head is facing. + * + * @param storeResult storage for the result (modified if not null) + * @return a direction vector (unit vector in the physics link's local + * coordinates, either storeResult or a new vector) + */ + @Override + public Vector3f faceDirection(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + result.set(0f, 1f, -3f); + result.normalizeLocal(); + + return result; + } +} diff --git a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/package-info.java b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/package-info.java index 90d815c07..a08b45832 100644 --- a/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/package-info.java +++ b/MinieExamples/src/main/java/jme3utilities/minie/test/tunings/package-info.java @@ -1,31 +1,31 @@ -/* - Copyright (c) 2018-2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * DynamicAnimControl tunings for animated models used in the MinieExamples - * subproject. - */ -package jme3utilities.minie.test.tunings; +/* + Copyright (c) 2018-2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * DynamicAnimControl tunings for animated models used in the MinieExamples + * subproject. + */ +package jme3utilities.minie.test.tunings; diff --git a/MinieExamples/src/main/resources/Models/Puppet/license.txt b/MinieExamples/src/main/resources/Models/Puppet/license.txt index b68e7a06b..c0e50ee0d 100644 --- a/MinieExamples/src/main/resources/Models/Puppet/license.txt +++ b/MinieExamples/src/main/resources/Models/Puppet/license.txt @@ -1,11 +1,11 @@ -Licensing history for assets in MinieExamples/src/main/resources/Models/Puppet - -The puppet.blend model asset was created by Nathan Vegdahl as part of -the production of the short film "Sintel" and is -(c) Copyright Blender Foundation durian.blender.org - -The Blender Foundation released it under a Creative Commons Attribution 3.0 -license. For details, see https://durian.blender.org/sharing/ - -Stephen Gold converted the model to J3O format and releases the converted +Licensing history for assets in MinieExamples/src/main/resources/Models/Puppet + +The puppet.blend model asset was created by Nathan Vegdahl as part of +the production of the short film "Sintel" and is +(c) Copyright Blender Foundation durian.blender.org + +The Blender Foundation released it under a Creative Commons Attribution 3.0 +license. For details, see https://durian.blender.org/sharing/ + +Stephen Gold converted the model to J3O format and releases the converted file Puppet.j3o under the same Creative Commons Attribution 3.0 license. \ No newline at end of file diff --git a/MinieLibrary/build.gradle b/MinieLibrary/build.gradle index 497925fce..770063734 100644 --- a/MinieLibrary/build.gradle +++ b/MinieLibrary/build.gradle @@ -1,525 +1,525 @@ -// Note: "common.gradle" in the root project contains additional initialization -// for this project. This initialization is applied in the "build.gradle" -// of the root project. - -plugins { - id 'java-library' - id 'maven-publish' - id 'signing' -} - -ext { - // The URL from which native libraries should be copied: - - libbulletjmeUrl = 'https://github.com/stephengold/Libbulletjme/releases/download/18.5.2/' - //libbulletjmeUrl = 'file:///home/sgold/NetBeansProjects/Libbulletjme/dist/' // to test a local build - //libbulletjmeUrl = 'file:///c:\\users\\sgold\\My%20Documents\\NetBeansProjects\\Libbulletjme\\dist\\' // to test a local build - - jarType = '' - pdbWindows64 = '' - if (rootProject.hasProperty('btdebug')) { - // -Pbtdebug specified on the command line - - if (rootProject.hasProperty('dp')) { - // both -Pbtdebug and -Pdp specified - - configureAndroidBtfs('') // no DebugDp for Android yet - configureDesktopBtfs('DebugDp') - metadata = 'debugdp' + jmeTarget - - } else if (rootProject.hasProperty('pdb64')) { - // both -Pbtdebug and -Ppdb64 specified, but not -Pdp - - configureAndroidBtfs('') - configureDesktopBtfs('') - btfWindows64 = 'DebugSp' - pdbWindows64 = 'DebugSp' - metadata = 'debug' + jmeTarget - - } else { // -Pbtdebug specified, but not -Pdp nor -Ppdb64 - configureAndroidBtfs('DebugSp') - configureDesktopBtfs('DebugSp') - metadata = 'debug' + jmeTarget - } - - } else if (rootProject.hasProperty('dp')) { - // -Pdp specified, but not -Pbtdebug - - configureAndroidBtfs('') // no ReleaseDp for Android yet - configureDesktopBtfs('ReleaseDp') - metadata = 'dp' + jmeTarget - - } else if (rootProject.hasProperty('bare')) { - // -Pbare specified, but neither -Pbtdebug nor -Pdp - - configureAndroidBtfs('') - configureDesktopBtfs('') - metadata = 'bare' + jmeTarget - - } else if (rootProject.hasProperty('big3')) { - // -Pbig3 specified, but neither -Pbtdebug nor -Pdp nor -Pbare - - configureAndroidBtfs('') - configureDesktopBtfs('') - btfLinux64 = 'ReleaseSp' - btfMacOSX64 = 'ReleaseSp' - btfWindows64 = 'ReleaseSp' - metadata = 'big3' + jmeTarget - - } else if (rootProject.hasProperty('mt')) { - // -Pmt specified, but neither -Pbtdebug nor -Pdp nor -Pbare nor -Pbig3 - - configureAndroidBtfs('') - configureDesktopBtfs('') - btfLinux64 = 'ReleaseSpMt' - btfWindows64 = 'ReleaseSpMt' - metadata = 'mt' + jmeTarget - - } else if (rootProject.hasProperty('droid')) { - // -Pdroid specified, but neither -Pbtdebug nor -Pdp nor -Pbare nor -Pbig3 nor -Pmt - - configureAndroidBtfs('ReleaseSp') - configureDesktopBtfs('') - metadata = 'droid' + jmeTarget - - } else { // neither -Pbtdebug nor -Pdp nor -Pbare nor -Pbig3 nor -Pmt nor -Pdroid specified - - // Specify the BTF (buildType + flavor) of native library for each platform: - // (Specify '' for no native library.) - - btfAndroid_ARM7 = 'ReleaseSp' - btfAndroid_ARM8 = 'ReleaseSp' - btfAndroid_X86 = 'ReleaseSp' - btfAndroid_X86_64 = 'ReleaseSp' - btfLinux32 = 'ReleaseSp' - btfLinux64 = 'ReleaseSp' - btfLinux_ARM32 = 'hfReleaseSp' - btfLinux_ARM64 = 'ReleaseSp' - btfMacOSX32 = 'ReleaseSp' - btfMacOSX64 = 'ReleaseSp' - btfMacOSX_ARM64 = 'ReleaseSp' - btfWindows32 = 'ReleaseSp' - btfWindows64 = 'ReleaseSp' - - metadata = jmeTarget - } - - group = 'com.github.stephengold' - artifact = 'Minie' - - if (metadata.isEmpty()) { - version = minieVersion - } else if (metadata.startsWith('+')) { - version = minieVersion + metadata - } else { - version = "${minieVersion}+${metadata}" - } - baseName = "${artifact}-${version}${minieSnapshot}" // for artifacts - - websiteUrl = 'https://github.com/stephengold/Minie' - - resourcesDir = 'src/main/resources/' - libDir = resourcesDir + 'lib/' // for Android native libraries - nativeDir = resourcesDir + 'native/' // for desktop native libraries -} - -dependencies { - api heartCoordinates - api 'com.simsilica:sim-math:1.6.0' - api 'org.jmonkeyengine:jme3-core:' + jme3Version - api 'org.jmonkeyengine:jme3-terrain:' + jme3Version - - testImplementation junitCoordinates - testImplementation 'org.jmonkeyengine:jme3-desktop:' + jme3Version - testImplementation 'org.jmonkeyengine:jme3-plugins:' + jme3Version - testRuntimeOnly testdataCoordinates -} - -test { - failFast true // stop after first failure - forkEvery 1 // don't run tests in parallel - testLogging { - events 'started', 'skipped', 'failed' - } -} - -processResources.dependsOn((btfAndroid_ARM7 == '' ? 'clean' : 'download') + 'Android_ARM7') -processResources.dependsOn((btfAndroid_ARM8 == '' ? 'clean' : 'download') + 'Android_ARM8') -processResources.dependsOn((btfAndroid_X86 == '' ? 'clean' : 'download') + 'Android_X86') -processResources.dependsOn((btfAndroid_X86_64 == '' ? 'clean' : 'download') + 'Android_X86_64') -processResources.dependsOn((btfLinux32 == '' ? 'clean' : 'download') + 'Linux32') -processResources.dependsOn((btfLinux64 == '' ? 'clean' : 'download') + 'Linux64') -processResources.dependsOn((btfLinux_ARM32 == '' ? 'clean' : 'download') + 'Linux_ARM32') -processResources.dependsOn((btfLinux_ARM64 == '' ? 'clean' : 'download') + 'Linux_ARM64') -processResources.dependsOn((btfMacOSX32 == '' ? 'clean' : 'download') + 'MacOSX32') -processResources.dependsOn((btfMacOSX64 == '' ? 'clean' : 'download') + 'MacOSX64') -processResources.dependsOn((btfMacOSX_ARM64 == '' ? 'clean' : 'download') + 'MacOSX_ARM64') -processResources.dependsOn((btfWindows32 == '' ? 'clean' : 'download') + 'Windows32') -processResources.dependsOn((btfWindows64 == '' ? 'clean' : 'download') + 'Windows64') - -assemble.dependsOn((pdbWindows64 == '' ? 'clean' : 'download') + 'PDBs') - -// tasks to download native libraries (from GitHub, typically) - -tasks.register('downloadAndroid_ARM7', MyDownload) { - sourceUrl = libbulletjmeUrl + "Android_ARM7${btfAndroid_ARM7}_libbulletjme.so" - target = file(libDir + 'armeabi-v7a/libbulletjme.so') -} -tasks.register('downloadAndroid_ARM8', MyDownload) { - sourceUrl = libbulletjmeUrl + "Android_ARM8${btfAndroid_ARM8}_libbulletjme.so" - target = file(libDir + 'arm64-v8a/libbulletjme.so') -} -tasks.register('downloadAndroid_X86', MyDownload) { - sourceUrl = libbulletjmeUrl + "Android_X86${btfAndroid_X86}_libbulletjme.so" - target = file(libDir + 'x86/libbulletjme.so') -} -tasks.register('downloadAndroid_X86_64', MyDownload) { - sourceUrl = libbulletjmeUrl + "Android_X86_64${btfAndroid_X86_64}_libbulletjme.so" - target = file(libDir + 'x86_64/libbulletjme.so') -} -tasks.register('downloadLinux32', MyDownload) { - sourceUrl = libbulletjmeUrl + "Linux32${btfLinux32}_libbulletjme.so" - target = file(nativeDir + 'linux/x86/libbulletjme.so') -} -tasks.register('downloadLinux64', MyDownload) { - sourceUrl = libbulletjmeUrl + "Linux64${btfLinux64}_libbulletjme.so" - target = file(nativeDir + 'linux/x86_64/libbulletjme.so') -} -tasks.register('downloadLinux_ARM32', MyDownload) { - sourceUrl = libbulletjmeUrl + "Linux_ARM32${btfLinux_ARM32}_libbulletjme.so" - target = file(nativeDir + 'linux/arm32/libbulletjme.so') -} -tasks.register('downloadLinux_ARM64', MyDownload) { - sourceUrl = libbulletjmeUrl + "Linux_ARM64${btfLinux_ARM64}_libbulletjme.so" - target = file(nativeDir + 'linux/arm64/libbulletjme.so') -} -tasks.register('downloadMacOSX32', MyDownload) { - sourceUrl = libbulletjmeUrl + "MacOSX32${btfMacOSX32}_libbulletjme.dylib" - target = file(nativeDir + 'osx/x86/libbulletjme.dylib') -} -tasks.register('downloadMacOSX64', MyDownload) { - sourceUrl = libbulletjmeUrl + "MacOSX64${btfMacOSX64}_libbulletjme.dylib" - target = file(nativeDir + 'osx/x86_64/libbulletjme.dylib') -} -tasks.register('downloadMacOSX_ARM64', MyDownload) { - sourceUrl = libbulletjmeUrl + "MacOSX_ARM64${btfMacOSX_ARM64}_libbulletjme.dylib" - target = file(nativeDir + 'osx/arm64/libbulletjme.dylib') -} -tasks.register('downloadWindows32', MyDownload) { - sourceUrl = libbulletjmeUrl + "Windows32${btfWindows32}_bulletjme.dll" - target = file(nativeDir + 'windows/x86/bulletjme.dll') -} -tasks.register('downloadWindows64', MyDownload) { - sourceUrl = libbulletjmeUrl + "Windows64${btfWindows64}_bulletjme.dll" - target = file(nativeDir + 'windows/x86_64/bulletjme.dll') -} - -tasks.register('downloadPDBs', MyDownload) { - sourceUrl = libbulletjmeUrl + "Windows64${pdbWindows64}_bulletjme.pdb" - target = file('bulletjme.pdb') -} - -// cleanup tasks - -clean.dependsOn('cleanAndroid_ARM7', 'cleanAndroid_ARM8', 'cleanAndroid_X86', - 'cleanAndroid_X86_64', 'cleanLinux32', 'cleanLinux64', - 'cleanLinux_ARM32', 'cleanLinux_ARM64', - 'cleanMacOSX32', 'cleanMacOSX64', 'cleanMacOSX_ARM64', - 'cleanWindows32', 'cleanWindows64') - -tasks.register('cleanAndroid_ARM7', Delete) { - delete libDir + 'armeabi-v7a/libbulletjme.so' -} -tasks.register('cleanAndroid_ARM8', Delete) { - delete libDir + 'arm64-v8a/libbulletjme.so' -} -tasks.register('cleanAndroid_X86', Delete) { - delete libDir + 'x86/libbulletjme.so' -} -tasks.register('cleanAndroid_X86_64', Delete) { - delete libDir + 'x86_64/libbulletjme.so' -} -tasks.register('cleanLinux32', Delete) { - delete nativeDir + 'linux/x86/libbulletjme.so' -} -tasks.register('cleanLinux64', Delete) { - delete nativeDir + 'linux/x86_64/libbulletjme.so' -} -tasks.register('cleanLinux_ARM32', Delete) { - delete nativeDir + 'linux/arm32/libbulletjme.so' -} -tasks.register('cleanLinux_ARM64', Delete) { - delete nativeDir + 'linux/arm64/libbulletjme.so' -} -tasks.register('cleanMacOSX32', Delete) { - delete nativeDir + 'osx/x86/libbulletjme.dylib' -} -tasks.register('cleanMacOSX64', Delete) { - delete nativeDir + 'osx/x86_64/libbulletjme.dylib' -} -tasks.register('cleanMacOSX_ARM64', Delete) { - delete nativeDir + 'osx/arm64/libbulletjme.dylib' -} -tasks.register('cleanWindows32', Delete) { - delete nativeDir + 'windows/x86/bulletjme.dll' -} -tasks.register('cleanWindows64', Delete) { - delete nativeDir + 'windows/x86_64/bulletjme.dll' -} - -// javadoc to (web)site tasks, triggered by push-master.yml - -tasks.register('copyJavadocToSite') { - dependsOn 'copyMasterJavadocToSite', \ - 'copy76JavadocToSite', \ - 'copy75JavadocToSite', \ - 'copy74JavadocToSite', \ - 'copy72JavadocToSite', \ - 'copy71JavadocToSite', \ - 'copy70JavadocToSite', \ - 'copy62JavadocToSite', \ - 'copy61JavadocToSite', \ - 'copy60JavadocToSite', \ - 'copy51JavadocToSite', \ - 'copy50JavadocToSite', \ - 'copy49JavadocToSite', \ - 'copy48JavadocToSite', \ - 'copy47JavadocToSite', \ - 'copy46JavadocToSite', \ - 'copy45JavadocToSite', \ - 'copy44JavadocToSite', \ - 'copy43JavadocToSite', \ - 'copy42JavadocToSite', \ - 'copy41JavadocToSite', \ - 'copy40JavadocToSite', \ - 'copy31JavadocToSite', \ - 'copy30JavadocToSite', \ - 'copy20JavadocToSite' -} -tasks.register('copyMasterJavadocToSite', Copy) { - dependsOn 'javadoc' - from "${buildDir}/docs/javadoc" - into '../build/site/javadoc/master' -} -defineJavadocTasksForRelease('7', '6', '0') -defineJavadocTasksForRelease('7', '5', '0') -defineJavadocTasksForRelease('7', '4', '0') -defineJavadocTasksForRelease('7', '2', '0') -defineJavadocTasksForRelease('7', '1', '0') -defineJavadocTasksForRelease('7', '0', '2') -defineJavadocTasksForRelease('6', '2', '0') -defineJavadocTasksForRelease('6', '1', '0') -defineJavadocTasksForRelease('6', '0', '1') -defineJavadocTasksForRelease('5', '1', '0') -defineJavadocTasksForRelease('5', '0', '1') -defineJavadocTasksForRelease('4', '9', '0') -defineJavadocTasksForRelease('4', '8', '1') -defineJavadocTasksForRelease('4', '7', '1') -defineJavadocTasksForRelease('4', '6', '1') -defineJavadocTasksForRelease('4', '5', '0') -defineJavadocTasksForRelease('4', '4', '0') -defineJavadocTasksForRelease('4', '3', '0') -defineJavadocTasksForRelease('4', '2', '0') -defineJavadocTasksForRelease('4', '1', '1') -defineJavadocTasksForRelease('4', '0', '2') -defineJavadocTasksForRelease('3', '1', '0') -defineJavadocTasksForRelease('3', '0', '0') -defineJavadocTasksForRelease('2', '0', '1') - -// publishing tasks: - -tasks.register('install') { - dependsOn 'publishMavenPublicationToMavenLocal' - description 'Installs Maven artifacts to the local repository.' -} -tasks.register('release') { - dependsOn 'publishMavenPublicationToOSSRHRepository' - description 'Stages Maven artifacts to Sonatype OSSRH.' -} - -jar { - archiveBaseName = project.ext.baseName - exclude('**/empty') - if (project.ext.jarType == 'nativesOnly') { - exclude '**/*.class' // no class files - includeEmptyDirs false - } - manifest { - attributes 'Created-By': "${JavaVersion.current()} (${System.getProperty("java.vendor")})" - } -} -tasks.register('javadocJar', Jar) { - archiveBaseName = project.ext.baseName - archiveClassifier = 'javadoc' - dependsOn 'javadoc' - description 'Creates a JAR of javadoc.' - from javadoc.destinationDir -} -tasks.register('sourcesJar', Jar) { - archiveBaseName = project.ext.baseName - archiveClassifier = 'sources' - description 'Creates a JAR of sourcecode.' - from sourceSets.main.allJava -} - -assemble.dependsOn('module', 'moduleAsc', 'pom', 'pomAsc') -tasks.register('module', Copy) { - dependsOn 'generateMetadataFileForMavenPublication' - description 'Copies the module metadata to build/libs.' - from "${buildDir}/publications/maven/module.json" - into "${buildDir}/libs" - rename 'module.json', project.ext.baseName + '.module' -} -tasks.register('moduleAsc', Copy) { - dependsOn 'signMavenPublication' - description 'Copies the signature of the module metadata to build/libs.' - from "${buildDir}/publications/maven/module.json.asc" - into "${buildDir}/libs" - rename 'module.json.asc', project.ext.baseName + '.module.asc' -} -tasks.register('pom', Copy) { - dependsOn 'generatePomFileForMavenPublication' - description 'Copies the Maven POM to build/libs.' - from "${buildDir}/publications/maven/pom-default.xml" - into "${buildDir}/libs" - rename 'pom-default.xml', project.ext.baseName + '.pom' -} -tasks.register('pomAsc', Copy) { - dependsOn 'signMavenPublication' - description 'Copies the signature of the Maven POM to build/libs.' - from "${buildDir}/publications/maven/pom-default.xml.asc" - into "${buildDir}/libs" - rename 'pom-default.xml.asc', project.ext.baseName + '.pom.asc' -} - -publishing { - publications { - maven(MavenPublication) { - artifact javadocJar - artifact sourcesJar - artifactId artifact - from components.java - groupId project.ext.group - pom { - description = 'a physics library for jMonkeyEngine' - developers { - developer { - email = 'sgold@sonic.net' - name = 'Stephen Gold' - } - } - licenses { - license { - distribution = 'repo' - name = 'New BSD (3-clause) License' - url = 'https://opensource.org/licenses/BSD-3-Clause' - } - } - name = project.ext.group + ':' + artifact - scm { - connection = 'scm:git:git://github.com/stephengold/Minie.git' - developerConnection = 'scm:git:ssh://github.com:stephengold/Minie.git' - url = project.ext.websiteUrl + '/tree/master' - } - url = 'https://stephengold.github.io/Minie' - } - version project.ext.version + rootProject.ext.minieSnapshot - } - } - // Staging to OSSRH relies on the existence of 2 properties - // (ossrhUsername and ossrhPassword) - // which should be stored in ~/.gradle/gradle.properties - repositories { - maven { - credentials { - username = project.hasProperty('ossrhUsername') ? ossrhUsername : 'Unknown user' - password = project.hasProperty('ossrhPassword') ? ossrhPassword : 'Unknown password' - } - name = 'OSSRH' - url = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2' - } - } -} -generateMetadataFileForMavenPublication.dependsOn('pom') -publishMavenPublicationToMavenLocal.dependsOn('assemble') -publishMavenPublicationToMavenLocal.doLast { - println 'installed locally as ' + project.ext.baseName -} -publishMavenPublicationToOSSRHRepository.dependsOn('assemble') - -// signing tasks: - -// Signing relies on the existence of 3 properties -// (signing.keyId, signing.password, and signing.secretKeyRingFile) -// which should be stored in ~/.gradle/gradle.properties - -signing { - sign publishing.publications.maven -} -tasks.withType(Sign) { - onlyIf { rootProject.hasProperty('signing.keyId') } -} -signMavenPublication.dependsOn('module') - -// helper class to wrap Ant download task - -class MyDownload extends DefaultTask { - @Input - String sourceUrl - - @OutputFile - File target - - @TaskAction - void download() { - ant.get(src: sourceUrl, dest: target) - println sourceUrl - } -} - -// helper methods to configure native libraries - -void configureAndroidBtfs(String btfValue) { - project.ext.btfAndroid_ARM7 = btfValue - project.ext.btfAndroid_ARM8 = btfValue - project.ext.btfAndroid_X86 = btfValue - project.ext.btfAndroid_X86_64 = btfValue -} - -void configureDesktopBtfs(String btfValue) { - project.ext.btfLinux32 = btfValue - if (btfValue.isEmpty()) { - project.ext.btfLinux64 = '' - project.ext.btfLinux_ARM32 = '' - project.ext.btfWindows64 = '' - } else { - project.ext.btfLinux64 = btfValue - project.ext.btfLinux_ARM32 = 'hf' + btfValue - project.ext.btfWindows64 = btfValue - } - project.ext.btfLinux_ARM64 = btfValue - project.ext.btfMacOSX32 = btfValue - project.ext.btfMacOSX64 = btfValue - project.ext.btfMacOSX_ARM64 = btfValue - project.ext.btfWindows32 = btfValue -} - -// helper method to register tasks that copy javadoc to the (web)site - -void defineJavadocTasksForRelease(String major, String minor, String patch) { - String downloadTaskName = "download${major}${minor}Javadoc" - String downloadUrl = 'https://github.com/stephengold/Minie/releases/download' - String jarPath = "${buildDir}/docs/v${major}-${minor}.jar" - String mmp = "${major}.${minor}.${patch}" - - tasks.register(downloadTaskName, MyDownload) { - sourceUrl = "${downloadUrl}/${mmp}/Minie-${mmp}-javadoc.jar" - target = file(jarPath) - } - - tasks.register("copy${major}${minor}JavadocToSite", Copy) { - dependsOn downloadTaskName - from zipTree(jarPath) - into "../build/site/javadoc/v${major}-${minor}" - } -} +// Note: "common.gradle" in the root project contains additional initialization +// for this project. This initialization is applied in the "build.gradle" +// of the root project. + +plugins { + id 'java-library' + id 'maven-publish' + id 'signing' +} + +ext { + // The URL from which native libraries should be copied: + + libbulletjmeUrl = 'https://github.com/stephengold/Libbulletjme/releases/download/18.5.2/' + //libbulletjmeUrl = 'file:///home/sgold/NetBeansProjects/Libbulletjme/dist/' // to test a local build + //libbulletjmeUrl = 'file:///c:\\users\\sgold\\My%20Documents\\NetBeansProjects\\Libbulletjme\\dist\\' // to test a local build + + jarType = '' + pdbWindows64 = '' + if (rootProject.hasProperty('btdebug')) { + // -Pbtdebug specified on the command line + + if (rootProject.hasProperty('dp')) { + // both -Pbtdebug and -Pdp specified + + configureAndroidBtfs('') // no DebugDp for Android yet + configureDesktopBtfs('DebugDp') + metadata = 'debugdp' + jmeTarget + + } else if (rootProject.hasProperty('pdb64')) { + // both -Pbtdebug and -Ppdb64 specified, but not -Pdp + + configureAndroidBtfs('') + configureDesktopBtfs('') + btfWindows64 = 'DebugSp' + pdbWindows64 = 'DebugSp' + metadata = 'debug' + jmeTarget + + } else { // -Pbtdebug specified, but not -Pdp nor -Ppdb64 + configureAndroidBtfs('DebugSp') + configureDesktopBtfs('DebugSp') + metadata = 'debug' + jmeTarget + } + + } else if (rootProject.hasProperty('dp')) { + // -Pdp specified, but not -Pbtdebug + + configureAndroidBtfs('') // no ReleaseDp for Android yet + configureDesktopBtfs('ReleaseDp') + metadata = 'dp' + jmeTarget + + } else if (rootProject.hasProperty('bare')) { + // -Pbare specified, but neither -Pbtdebug nor -Pdp + + configureAndroidBtfs('') + configureDesktopBtfs('') + metadata = 'bare' + jmeTarget + + } else if (rootProject.hasProperty('big3')) { + // -Pbig3 specified, but neither -Pbtdebug nor -Pdp nor -Pbare + + configureAndroidBtfs('') + configureDesktopBtfs('') + btfLinux64 = 'ReleaseSp' + btfMacOSX64 = 'ReleaseSp' + btfWindows64 = 'ReleaseSp' + metadata = 'big3' + jmeTarget + + } else if (rootProject.hasProperty('mt')) { + // -Pmt specified, but neither -Pbtdebug nor -Pdp nor -Pbare nor -Pbig3 + + configureAndroidBtfs('') + configureDesktopBtfs('') + btfLinux64 = 'ReleaseSpMt' + btfWindows64 = 'ReleaseSpMt' + metadata = 'mt' + jmeTarget + + } else if (rootProject.hasProperty('droid')) { + // -Pdroid specified, but neither -Pbtdebug nor -Pdp nor -Pbare nor -Pbig3 nor -Pmt + + configureAndroidBtfs('ReleaseSp') + configureDesktopBtfs('') + metadata = 'droid' + jmeTarget + + } else { // neither -Pbtdebug nor -Pdp nor -Pbare nor -Pbig3 nor -Pmt nor -Pdroid specified + + // Specify the BTF (buildType + flavor) of native library for each platform: + // (Specify '' for no native library.) + + btfAndroid_ARM7 = 'ReleaseSp' + btfAndroid_ARM8 = 'ReleaseSp' + btfAndroid_X86 = 'ReleaseSp' + btfAndroid_X86_64 = 'ReleaseSp' + btfLinux32 = 'ReleaseSp' + btfLinux64 = 'ReleaseSp' + btfLinux_ARM32 = 'hfReleaseSp' + btfLinux_ARM64 = 'ReleaseSp' + btfMacOSX32 = 'ReleaseSp' + btfMacOSX64 = 'ReleaseSp' + btfMacOSX_ARM64 = 'ReleaseSp' + btfWindows32 = 'ReleaseSp' + btfWindows64 = 'ReleaseSp' + + metadata = jmeTarget + } + + group = 'com.github.stephengold' + artifact = 'Minie' + + if (metadata.isEmpty()) { + version = minieVersion + } else if (metadata.startsWith('+')) { + version = minieVersion + metadata + } else { + version = "${minieVersion}+${metadata}" + } + baseName = "${artifact}-${version}${minieSnapshot}" // for artifacts + + websiteUrl = 'https://github.com/stephengold/Minie' + + resourcesDir = 'src/main/resources/' + libDir = resourcesDir + 'lib/' // for Android native libraries + nativeDir = resourcesDir + 'native/' // for desktop native libraries +} + +dependencies { + api heartCoordinates + api 'com.simsilica:sim-math:1.6.0' + api 'org.jmonkeyengine:jme3-core:' + jme3Version + api 'org.jmonkeyengine:jme3-terrain:' + jme3Version + + testImplementation junitCoordinates + testImplementation 'org.jmonkeyengine:jme3-desktop:' + jme3Version + testImplementation 'org.jmonkeyengine:jme3-plugins:' + jme3Version + testRuntimeOnly testdataCoordinates +} + +test { + failFast true // stop after first failure + forkEvery 1 // don't run tests in parallel + testLogging { + events 'started', 'skipped', 'failed' + } +} + +processResources.dependsOn((btfAndroid_ARM7 == '' ? 'clean' : 'download') + 'Android_ARM7') +processResources.dependsOn((btfAndroid_ARM8 == '' ? 'clean' : 'download') + 'Android_ARM8') +processResources.dependsOn((btfAndroid_X86 == '' ? 'clean' : 'download') + 'Android_X86') +processResources.dependsOn((btfAndroid_X86_64 == '' ? 'clean' : 'download') + 'Android_X86_64') +processResources.dependsOn((btfLinux32 == '' ? 'clean' : 'download') + 'Linux32') +processResources.dependsOn((btfLinux64 == '' ? 'clean' : 'download') + 'Linux64') +processResources.dependsOn((btfLinux_ARM32 == '' ? 'clean' : 'download') + 'Linux_ARM32') +processResources.dependsOn((btfLinux_ARM64 == '' ? 'clean' : 'download') + 'Linux_ARM64') +processResources.dependsOn((btfMacOSX32 == '' ? 'clean' : 'download') + 'MacOSX32') +processResources.dependsOn((btfMacOSX64 == '' ? 'clean' : 'download') + 'MacOSX64') +processResources.dependsOn((btfMacOSX_ARM64 == '' ? 'clean' : 'download') + 'MacOSX_ARM64') +processResources.dependsOn((btfWindows32 == '' ? 'clean' : 'download') + 'Windows32') +processResources.dependsOn((btfWindows64 == '' ? 'clean' : 'download') + 'Windows64') + +assemble.dependsOn((pdbWindows64 == '' ? 'clean' : 'download') + 'PDBs') + +// tasks to download native libraries (from GitHub, typically) + +tasks.register('downloadAndroid_ARM7', MyDownload) { + sourceUrl = libbulletjmeUrl + "Android_ARM7${btfAndroid_ARM7}_libbulletjme.so" + target = file(libDir + 'armeabi-v7a/libbulletjme.so') +} +tasks.register('downloadAndroid_ARM8', MyDownload) { + sourceUrl = libbulletjmeUrl + "Android_ARM8${btfAndroid_ARM8}_libbulletjme.so" + target = file(libDir + 'arm64-v8a/libbulletjme.so') +} +tasks.register('downloadAndroid_X86', MyDownload) { + sourceUrl = libbulletjmeUrl + "Android_X86${btfAndroid_X86}_libbulletjme.so" + target = file(libDir + 'x86/libbulletjme.so') +} +tasks.register('downloadAndroid_X86_64', MyDownload) { + sourceUrl = libbulletjmeUrl + "Android_X86_64${btfAndroid_X86_64}_libbulletjme.so" + target = file(libDir + 'x86_64/libbulletjme.so') +} +tasks.register('downloadLinux32', MyDownload) { + sourceUrl = libbulletjmeUrl + "Linux32${btfLinux32}_libbulletjme.so" + target = file(nativeDir + 'linux/x86/libbulletjme.so') +} +tasks.register('downloadLinux64', MyDownload) { + sourceUrl = libbulletjmeUrl + "Linux64${btfLinux64}_libbulletjme.so" + target = file(nativeDir + 'linux/x86_64/libbulletjme.so') +} +tasks.register('downloadLinux_ARM32', MyDownload) { + sourceUrl = libbulletjmeUrl + "Linux_ARM32${btfLinux_ARM32}_libbulletjme.so" + target = file(nativeDir + 'linux/arm32/libbulletjme.so') +} +tasks.register('downloadLinux_ARM64', MyDownload) { + sourceUrl = libbulletjmeUrl + "Linux_ARM64${btfLinux_ARM64}_libbulletjme.so" + target = file(nativeDir + 'linux/arm64/libbulletjme.so') +} +tasks.register('downloadMacOSX32', MyDownload) { + sourceUrl = libbulletjmeUrl + "MacOSX32${btfMacOSX32}_libbulletjme.dylib" + target = file(nativeDir + 'osx/x86/libbulletjme.dylib') +} +tasks.register('downloadMacOSX64', MyDownload) { + sourceUrl = libbulletjmeUrl + "MacOSX64${btfMacOSX64}_libbulletjme.dylib" + target = file(nativeDir + 'osx/x86_64/libbulletjme.dylib') +} +tasks.register('downloadMacOSX_ARM64', MyDownload) { + sourceUrl = libbulletjmeUrl + "MacOSX_ARM64${btfMacOSX_ARM64}_libbulletjme.dylib" + target = file(nativeDir + 'osx/arm64/libbulletjme.dylib') +} +tasks.register('downloadWindows32', MyDownload) { + sourceUrl = libbulletjmeUrl + "Windows32${btfWindows32}_bulletjme.dll" + target = file(nativeDir + 'windows/x86/bulletjme.dll') +} +tasks.register('downloadWindows64', MyDownload) { + sourceUrl = libbulletjmeUrl + "Windows64${btfWindows64}_bulletjme.dll" + target = file(nativeDir + 'windows/x86_64/bulletjme.dll') +} + +tasks.register('downloadPDBs', MyDownload) { + sourceUrl = libbulletjmeUrl + "Windows64${pdbWindows64}_bulletjme.pdb" + target = file('bulletjme.pdb') +} + +// cleanup tasks + +clean.dependsOn('cleanAndroid_ARM7', 'cleanAndroid_ARM8', 'cleanAndroid_X86', + 'cleanAndroid_X86_64', 'cleanLinux32', 'cleanLinux64', + 'cleanLinux_ARM32', 'cleanLinux_ARM64', + 'cleanMacOSX32', 'cleanMacOSX64', 'cleanMacOSX_ARM64', + 'cleanWindows32', 'cleanWindows64') + +tasks.register('cleanAndroid_ARM7', Delete) { + delete libDir + 'armeabi-v7a/libbulletjme.so' +} +tasks.register('cleanAndroid_ARM8', Delete) { + delete libDir + 'arm64-v8a/libbulletjme.so' +} +tasks.register('cleanAndroid_X86', Delete) { + delete libDir + 'x86/libbulletjme.so' +} +tasks.register('cleanAndroid_X86_64', Delete) { + delete libDir + 'x86_64/libbulletjme.so' +} +tasks.register('cleanLinux32', Delete) { + delete nativeDir + 'linux/x86/libbulletjme.so' +} +tasks.register('cleanLinux64', Delete) { + delete nativeDir + 'linux/x86_64/libbulletjme.so' +} +tasks.register('cleanLinux_ARM32', Delete) { + delete nativeDir + 'linux/arm32/libbulletjme.so' +} +tasks.register('cleanLinux_ARM64', Delete) { + delete nativeDir + 'linux/arm64/libbulletjme.so' +} +tasks.register('cleanMacOSX32', Delete) { + delete nativeDir + 'osx/x86/libbulletjme.dylib' +} +tasks.register('cleanMacOSX64', Delete) { + delete nativeDir + 'osx/x86_64/libbulletjme.dylib' +} +tasks.register('cleanMacOSX_ARM64', Delete) { + delete nativeDir + 'osx/arm64/libbulletjme.dylib' +} +tasks.register('cleanWindows32', Delete) { + delete nativeDir + 'windows/x86/bulletjme.dll' +} +tasks.register('cleanWindows64', Delete) { + delete nativeDir + 'windows/x86_64/bulletjme.dll' +} + +// javadoc to (web)site tasks, triggered by push-master.yml + +tasks.register('copyJavadocToSite') { + dependsOn 'copyMasterJavadocToSite', \ + 'copy76JavadocToSite', \ + 'copy75JavadocToSite', \ + 'copy74JavadocToSite', \ + 'copy72JavadocToSite', \ + 'copy71JavadocToSite', \ + 'copy70JavadocToSite', \ + 'copy62JavadocToSite', \ + 'copy61JavadocToSite', \ + 'copy60JavadocToSite', \ + 'copy51JavadocToSite', \ + 'copy50JavadocToSite', \ + 'copy49JavadocToSite', \ + 'copy48JavadocToSite', \ + 'copy47JavadocToSite', \ + 'copy46JavadocToSite', \ + 'copy45JavadocToSite', \ + 'copy44JavadocToSite', \ + 'copy43JavadocToSite', \ + 'copy42JavadocToSite', \ + 'copy41JavadocToSite', \ + 'copy40JavadocToSite', \ + 'copy31JavadocToSite', \ + 'copy30JavadocToSite', \ + 'copy20JavadocToSite' +} +tasks.register('copyMasterJavadocToSite', Copy) { + dependsOn 'javadoc' + from "${buildDir}/docs/javadoc" + into '../build/site/javadoc/master' +} +defineJavadocTasksForRelease('7', '6', '0') +defineJavadocTasksForRelease('7', '5', '0') +defineJavadocTasksForRelease('7', '4', '0') +defineJavadocTasksForRelease('7', '2', '0') +defineJavadocTasksForRelease('7', '1', '0') +defineJavadocTasksForRelease('7', '0', '2') +defineJavadocTasksForRelease('6', '2', '0') +defineJavadocTasksForRelease('6', '1', '0') +defineJavadocTasksForRelease('6', '0', '1') +defineJavadocTasksForRelease('5', '1', '0') +defineJavadocTasksForRelease('5', '0', '1') +defineJavadocTasksForRelease('4', '9', '0') +defineJavadocTasksForRelease('4', '8', '1') +defineJavadocTasksForRelease('4', '7', '1') +defineJavadocTasksForRelease('4', '6', '1') +defineJavadocTasksForRelease('4', '5', '0') +defineJavadocTasksForRelease('4', '4', '0') +defineJavadocTasksForRelease('4', '3', '0') +defineJavadocTasksForRelease('4', '2', '0') +defineJavadocTasksForRelease('4', '1', '1') +defineJavadocTasksForRelease('4', '0', '2') +defineJavadocTasksForRelease('3', '1', '0') +defineJavadocTasksForRelease('3', '0', '0') +defineJavadocTasksForRelease('2', '0', '1') + +// publishing tasks: + +tasks.register('install') { + dependsOn 'publishMavenPublicationToMavenLocal' + description 'Installs Maven artifacts to the local repository.' +} +tasks.register('release') { + dependsOn 'publishMavenPublicationToOSSRHRepository' + description 'Stages Maven artifacts to Sonatype OSSRH.' +} + +jar { + archiveBaseName = project.ext.baseName + exclude('**/empty') + if (project.ext.jarType == 'nativesOnly') { + exclude '**/*.class' // no class files + includeEmptyDirs false + } + manifest { + attributes 'Created-By': "${JavaVersion.current()} (${System.getProperty("java.vendor")})" + } +} +tasks.register('javadocJar', Jar) { + archiveBaseName = project.ext.baseName + archiveClassifier = 'javadoc' + dependsOn 'javadoc' + description 'Creates a JAR of javadoc.' + from javadoc.destinationDir +} +tasks.register('sourcesJar', Jar) { + archiveBaseName = project.ext.baseName + archiveClassifier = 'sources' + description 'Creates a JAR of sourcecode.' + from sourceSets.main.allJava +} + +assemble.dependsOn('module', 'moduleAsc', 'pom', 'pomAsc') +tasks.register('module', Copy) { + dependsOn 'generateMetadataFileForMavenPublication' + description 'Copies the module metadata to build/libs.' + from "${buildDir}/publications/maven/module.json" + into "${buildDir}/libs" + rename 'module.json', project.ext.baseName + '.module' +} +tasks.register('moduleAsc', Copy) { + dependsOn 'signMavenPublication' + description 'Copies the signature of the module metadata to build/libs.' + from "${buildDir}/publications/maven/module.json.asc" + into "${buildDir}/libs" + rename 'module.json.asc', project.ext.baseName + '.module.asc' +} +tasks.register('pom', Copy) { + dependsOn 'generatePomFileForMavenPublication' + description 'Copies the Maven POM to build/libs.' + from "${buildDir}/publications/maven/pom-default.xml" + into "${buildDir}/libs" + rename 'pom-default.xml', project.ext.baseName + '.pom' +} +tasks.register('pomAsc', Copy) { + dependsOn 'signMavenPublication' + description 'Copies the signature of the Maven POM to build/libs.' + from "${buildDir}/publications/maven/pom-default.xml.asc" + into "${buildDir}/libs" + rename 'pom-default.xml.asc', project.ext.baseName + '.pom.asc' +} + +publishing { + publications { + maven(MavenPublication) { + artifact javadocJar + artifact sourcesJar + artifactId artifact + from components.java + groupId project.ext.group + pom { + description = 'a physics library for jMonkeyEngine' + developers { + developer { + email = 'sgold@sonic.net' + name = 'Stephen Gold' + } + } + licenses { + license { + distribution = 'repo' + name = 'New BSD (3-clause) License' + url = 'https://opensource.org/licenses/BSD-3-Clause' + } + } + name = project.ext.group + ':' + artifact + scm { + connection = 'scm:git:git://github.com/stephengold/Minie.git' + developerConnection = 'scm:git:ssh://github.com:stephengold/Minie.git' + url = project.ext.websiteUrl + '/tree/master' + } + url = 'https://stephengold.github.io/Minie' + } + version project.ext.version + rootProject.ext.minieSnapshot + } + } + // Staging to OSSRH relies on the existence of 2 properties + // (ossrhUsername and ossrhPassword) + // which should be stored in ~/.gradle/gradle.properties + repositories { + maven { + credentials { + username = project.hasProperty('ossrhUsername') ? ossrhUsername : 'Unknown user' + password = project.hasProperty('ossrhPassword') ? ossrhPassword : 'Unknown password' + } + name = 'OSSRH' + url = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2' + } + } +} +generateMetadataFileForMavenPublication.dependsOn('pom') +publishMavenPublicationToMavenLocal.dependsOn('assemble') +publishMavenPublicationToMavenLocal.doLast { + println 'installed locally as ' + project.ext.baseName +} +publishMavenPublicationToOSSRHRepository.dependsOn('assemble') + +// signing tasks: + +// Signing relies on the existence of 3 properties +// (signing.keyId, signing.password, and signing.secretKeyRingFile) +// which should be stored in ~/.gradle/gradle.properties + +signing { + sign publishing.publications.maven +} +tasks.withType(Sign) { + onlyIf { rootProject.hasProperty('signing.keyId') } +} +signMavenPublication.dependsOn('module') + +// helper class to wrap Ant download task + +class MyDownload extends DefaultTask { + @Input + String sourceUrl + + @OutputFile + File target + + @TaskAction + void download() { + ant.get(src: sourceUrl, dest: target) + println sourceUrl + } +} + +// helper methods to configure native libraries + +void configureAndroidBtfs(String btfValue) { + project.ext.btfAndroid_ARM7 = btfValue + project.ext.btfAndroid_ARM8 = btfValue + project.ext.btfAndroid_X86 = btfValue + project.ext.btfAndroid_X86_64 = btfValue +} + +void configureDesktopBtfs(String btfValue) { + project.ext.btfLinux32 = btfValue + if (btfValue.isEmpty()) { + project.ext.btfLinux64 = '' + project.ext.btfLinux_ARM32 = '' + project.ext.btfWindows64 = '' + } else { + project.ext.btfLinux64 = btfValue + project.ext.btfLinux_ARM32 = 'hf' + btfValue + project.ext.btfWindows64 = btfValue + } + project.ext.btfLinux_ARM64 = btfValue + project.ext.btfMacOSX32 = btfValue + project.ext.btfMacOSX64 = btfValue + project.ext.btfMacOSX_ARM64 = btfValue + project.ext.btfWindows32 = btfValue +} + +// helper method to register tasks that copy javadoc to the (web)site + +void defineJavadocTasksForRelease(String major, String minor, String patch) { + String downloadTaskName = "download${major}${minor}Javadoc" + String downloadUrl = 'https://github.com/stephengold/Minie/releases/download' + String jarPath = "${buildDir}/docs/v${major}-${minor}.jar" + String mmp = "${major}.${minor}.${patch}" + + tasks.register(downloadTaskName, MyDownload) { + sourceUrl = "${downloadUrl}/${mmp}/Minie-${mmp}-javadoc.jar" + target = file(jarPath) + } + + tasks.register("copy${major}${minor}JavadocToSite", Copy) { + dependsOn downloadTaskName + from zipTree(jarPath) + into "../build/site/javadoc/v${major}-${minor}" + } +} diff --git a/MinieLibrary/release-notes-pre10.md b/MinieLibrary/release-notes-pre10.md index 1ef75bb4c..de36815d5 100644 --- a/MinieLibrary/release-notes-pre10.md +++ b/MinieLibrary/release-notes-pre10.md @@ -1,970 +1,970 @@ -# Release log for the Minie library, DacWizard, and MinieExamples - -release log continues at https://github.com/stephengold/Minie/blob/master/MinieLibrary/release-notes.md - -## Version 0.9.15for33 released on 29 August 2019 - - + API changes: - + Deprecated the `extrapolateTransform()` and `getPhysicsScale()` methods of - the `PhysicsRigidBody` class. - + Deleted the unused `OverlapListener` interface. - + Fixed bug: - + "body A does not exist" `NullPointerException` while loading a - `DynamicAnimControl` from a J3O. - + Added library features: - + `getScale()` and `getTransform()` methods for `PhysicsCollisionObject` - + Argument validation for `MeshCollisionShape` constructors - + Other improvements: - + Merged the functionality of `TestHullContact` into the - `MultiSphereDemo` application. - + Stopped overriding the default collision margin in `MultiSphereDemo`. - -## Version 0.9.14for33 released on 25 August 2019 - - + Simplified the construction of collision shapes from multiple meshes. - + Changed `DacLinks` to skip `setGravity()` on kinematic bodies. - + Added verification of the local copy of gravity in `PhysicsSpace` - when assertions are enabled. - + Updated the native libraries to version 2.0.7 of `Libbulletjme`. - + Based on version 3.0 of the `jme3-utilities-heart` library, version - 0.7.7 of the `jme3-utilities-ui` library, and version 0.9.9 of the - `jme3-utilities-nifty` library. - -## Version 0.9.13for33 released on 7 August 2019 - - + API changes: - + Finalized the `TorsoLink.countManaged()` method. - + Standardized `TranslationalLimitMotor.getAccumulatedImpulse()` to use - caller-provided storage. - + Fixed bugs: - + Various bugs in debug visualization, including one where shadows - were cast by visualizations of bounding boxes and swept spheres and one - where axes were visualized after shapes were no - longer visualized. - + A `NullPointerException` in `DacLinks.findManagerForVertex()`. - + A `NullPointerException` in `DacLinks.managerMap()`. - + An `AssertionError` in `PhysicsSpace.countJoints()`. - + An `AssertionError` caused by scaled compound shapes. - + User objects were not cloned/serialized, even if they - implement `JmeCloneable` or `Savable`. - + Motor-enable flags and accumulated impulses weren't properly - loaded/saved/cloned. - + Control not found in `TrackDemo`. - + A `NullPointerException` when changing models in `BalanceDemo`. - + Added library features: - + Support for V-HACD using Riccardo's Java bindings. - + A warning in case a joint is added to a `PhysicsSpace` before - the bodies that it joins. - + Constructors for box/cylinder/sphere shapes based on float buffers. - + Optional filtering of physics dumps. - + Dump CCD/sleep parameters of dynamic rigid bodies. - + `isEnabled()` and `setEnabled()` methods for `TranslationalLimitMotor`. - + A flag to dump motors. - + A `setAccumulatedImpulse()` method for `RotationalLimitMotor`. - + `FilterAll` methods `countExceptions()`, `defaultReturnValue()`, - and `listExceptions()`. - + A `UserFilter` class. - + Other improvements: - + Extended the `setDebugViewPorts()` method of `BulletAppState` to accept - multiple arguments. - + Added a `JointDemo` app. - + More thorough dumps/descriptions of joints, especially 6-DOF joints - and their motors. - + Enhanced the `BuoyDemo`, `MultiSphereDemo`, `RopeDemo`, `TestDac`, - and `TestSoftBody` apps with hotkeys to toggle debug visualization options. - + Added a hotkey to `MultiSphereDemo` to delete gems. - + Updated the native libraries to version 2.0.5 of `Libbulletjme`. - + Based on version 2.31 of the `jme3-utilities-heart` library, version - 0.7.6 of the `jme3-utilities-ui` library, and version 0.9.8 of the - `jme3-utilities-nifty` library. - -## Version 0.9.8for33 released on 17 July 2019 - - + Added an `update(float, int)` method to the `PhysicsSpace` class. - + Added `clearCache()`, `getDebugMesh()`, and `getDebugShape(CollisionShape)` - methods to the `DebugShapeFactory` class. - + Extended the constructors for `CompoundMesh`, `GImpactCollisionShape`, and - `HullCollisionShape` to accept multiple meshes. - + Updated the `TestHullContact` app to be more like a demo. - + Built using Gradle v5.5.1 . - -## Version 0.9.6for33 released on 13 July 2019 - - + Finalized the `getRigidBody()` method in the `PhysicsLink` - class. (API change) - + Allowed soft-body nodes to have mass=0 (for pinning). - + Changed the semantics of `RigidBodyControl.setKinematicSpatial()` - to match jme3-bullet. - + Added tutorial apps: `HelloSoftBody`, `HelloSoftSoft`, `HelloCloth`, - and `HelloSoftRope`. - + Added a `DividedLine` mesh class. - + Moved the `massForStatic` constant to the `PhysicsBody` class. - -## Version 0.9.5for33 released on 6 July 2019 - - + Modified `DynamicAnimControl` to work with armatures as well as skeletons. - + Fixed JME issue 1135 (`ConeJoint` causes rigid body to disappear). - + Fixed cloning bugs in `SoftPhysicsJoint` and `SoftBodyControl`. - + Removed 3 `jme3test` apps that now work unmodified with Minie. - + Added a `contains(PhysicsJoint)` method to the `PhysicsSpace` class. - + Added modified `TestRagDoll` and `TestGimpactShape` apps that - work with Minie. - + Added hotkey-binding hints to `TestHeightfield` and `TestHullContact`. - + Added a `TestStaticBody` test. - + Updated the native libraries to version 1.0.90 of `Libbulletjme`. - + Based on version 2.29 of the `jme3-utilities-heart` library. - + Based on version 3.3.0-alpha2 of jMonkeyEngine. - -## Version 0.9.4 released on 2 July 2019 - - + API changes: - + Made the `DebugAppStateFilter` interface compatible with jme3-bullet again. - + De-publicized the `PhysicsSpace.setLocalThreadPhysicsSpace()` method. - + Removed the `createTriangleIndexVertexArray()` method from the - `NativeMeshUtil` class. - + Re-implemented anchors as a kind of `PhysicsJoint`. - + Renamed the `BulletJointDebugControl` class. - + Removed the `updateAnchorMesh()` method from the - `NativeSoftBodyUtil` class. - + Fixed bugs: - + JME issue 1120 (scaled `GImpactCollisionShape` gets incorrect bounding box) - + bounding box of a shape not updated for `getAabb()` - + JME issue 1125 (inaccurate visualization of `HeightfieldCollisionShape`) - + clusters/joints/nodes of a soft body not cloned/serialized properly - + tau and impulseClamp of a `Point2PointJoint` not de-serialized properly - + 7 limit-motor parameters of a `SixDofJoint` not serialized properly - + JME issues 1126 and 1127 (`TestHoverTank` crash and reset action) - + Added library features: - + new classes `SoftAngularJoint` and `SoftLinearJoint` for soft-body joints - + new classes `CompoundMesh` and `IndexedMesh` for native meshes - + select single-sided/double-sided debug visualization materials for each - collision object - + an exception list for each `FilterAll` instance - + control which soft bodies have their clusters visualized - + a new constructor for a `HeightfieldCollisionShape` with additional options - + access the `BulletDebugAppState` associated with a `BulletAppState` - + access the `feedback` flag of each physics constraint - + access 6 per-cluster parameters - + access the per-constraint property that overrides the number of - solver iterations - + Other improvements: - + Added hotkey-binding hints to demo apps: press H to toggle hints. - + Bound the up/down arrow keys to control camera movement in - demo applications. - + Added a test for default values of newly created physics objects. - + Customized the `toString()` methods of the `CollisionShape`, - `PhysicsJoint`, and `PhysicsSpace` classes. - + Improved the output of `PhysicsDumper`. - + Customized the `equals()` and `hashCode()` methods of the - `CollisionShape` class. - + Avoided calling native code to fill zero-length buffers. - + Removed native libraries from the Git repository; download them - from GitHub instead. - + Updated the native libraries to version 1.0.89 of `Libbulletjme`. - -## Version 0.9.3 released on 11 June 2019 - - + Moved 2 tutorial apps to a new `jme3utilities.tutorial` package. - + Fixed bugs: - + Spatial transform not applied to static bodies in `RigidBodyControl`. - + `IllegalArgumentException` thrown when translating/rotating a - heightfield-shaped rigid body. - + Some physics controls ignore `isEnabled()`. - + After de-serializing a physics control, its `userObject` is null. - + `TestHeightfield` attached physics control to the root node. - + `TestHeightfield` used wrong logger. - + Added a `PhysicsSpace.destroy()` method for compatibility with jme3-bullet. - + Improved physics dumps. - + Removed uses of shared mutable "constants". - + Made various improvements to MinieExamples. - -## Version 0.9.2 released on 7 June 2019 - - + API changes: - + Privatized 5 fields in the `GhostControl` class. - + Removed the `rebuildSoftBody()` method from the `PhysicsSoftBody` class. - + Added a `Transform` argument to the `updateMesh()` method in the - `NativeSoftBodyUtil` class. - + New features for soft-body physics: - + Added a `SoftBodyControl` class. - + Allowed setting `maxSubSteps` to 0 for a variable-length time step. - + Added `maxTimeStep` parameter to `PhysicsSpace`, for use with a - variable-length time step. - + Added a warning when `setGravity()` is applied to a body that isn't - in any space. - + Publicized the `distributeEvents()` method of `PhysicsSpace`, for use in - non-`BulletAppState` applications. - + An `isEmpty()` method for `PhysicsSoftBody`. - + Built for compatibility with Java 7. - + Added debug visualization of soft-body anchors. - + Avoided cloning/serializing world info and gravity: adding to a physics space - would trash these data. - + Improvements to `DacWizard` and examples: - + Added a `TestSoftBodyControl` application. - + Made `ClothGrid` dynamic and added a `reposition()` method. - + Changed `ClothGrid` to minimize directional bias. - + Added Sony Duck model with license. - + Use LWJGL v3 to allow fullscreen mode on Linux systems (JME issue #947). - + Various improvements to physics dumps. - + Updated the native libraries to version 1.0.73 of `Libbulletjme`. - + Based on version 2.28.1 of the `jme3-utilities-heart` library. - -## Version 0.9.1 released on 28 May 2019 - - + API changes: - + Privatized the `motionState` field in the `PhysicsRigidBody` class. - + Removed methods from the `PhysicsSoftBody` class: - + `addAeroForceToNode()` - + `getPhysicsTransform()` - + `setPhysicsRotation()` - + `setPhysicsTransform()` - + Renamed the `PhysicsRigidBody.getPhysicsTransform()` method - to `extrapolateTransform()`. - + Moved the `SoftBodyWorldInfo` class to the `com.jme3.bullet` package. - + Converted the `PhysicsSoftBody.Config` class to an external class. - + Expanded soft-body physics: got aerodynamics and anchors working. - + Improved debug visualization of soft bodies: - + override default material for shapes if requested - + don't visualize links if the body has faces - + visualize clusters - + generate/update normals if requested - + Other changes to debug visualization: - + Ensured that static rigid bodies are visualized in blue. - + Changed the wireframe debug materials from single-sided to double-sided. - + Added a `debugMeshInitListener` option to add texture coordinates. - + Added a `MinieAssets` sub-project. - + Overrode the default `toString()` method for collision objects. - + Added methods to the `PhysicsSoftBody` class: - + `copyClusterMasses()` - + `countNodesInCluster()` - + `listNodesInCluster()` - + `setWindVelocity()` - + `windVelocity()` - + Added `Aero` and `ClothGrid` classes. - + Updated the native libraries to version 1.0.70 of `Libbulletjme`. - + Based on version 2.28 of the `jme3-utilities-heart` library. - -## Version 0.9.0 released on 14 May 2019 - - + Added a minimal implementation of soft-body physics (based on Dokthar's - prior work) that included `ConfigFlag`, `Icosphere`, `MeshEdge`, - `NativeSoftBodyUtil`, `NetGrid`, `PhysicsBody`, `PhysicsSoftBody`, - `PhysicsSoftSpace`, `RayTestFlag`, `Sbcp`, `SoftBodyWorldInfo`, - `SoftPhysicsAppState`, `SoftDebugAppState`, `SoftBodyDebugControl`, and - `TestSoftBody`. - + Re-publicized the `update()` method and finalized the `getSpaceId()` method - of `PhysicsSpace`. - + Moved the `isInWorld()` method from `PhysicsRigidBody` - to `PhysicsCollisionObject`. - + Moved `TubeTreeMesh` class to the `jme3utilities.minie.test.mesh` package. - + Used `BinaryExporter.saveAndLoad()` to simplify load/save testing. - + Updated the native libraries to version 1.0.61 of `Libbulletjme`. - + Based on version 2.27 of the `jme3-utilities-heart` library. - -## Version 0.8.1 released on 28 April 2019 - - + API changes: - + Privatized 2 protected fields in the `BulletDebugAppState` class. - + Privatized 7 protected fields in the `RigidBodyControl` class. - + Privatized 4 protected fields in the `VehicleControl` class. - + Renamed the `MyObject` class to `MyPco`. - + Removed the `BubbleControl`, `SimpleGhostControl`, - and `SimpleSolidControl` classes. - + De-publicized the `BoundingBoxDebugControl` constructor. - + Removed the `setPivot()` method from the `SixDofJoint` class. - + Changed the semantics of the `addAll()` and `removeAll()` methods in the - `PhysicsSpace` class; they no longer attempt to add/remove physics joints. - + Added a swept-sphere visualization feature. - + Added assertions to catch attempts to read the angular/linear velocities - of non-dynamic rigid bodies. - + Added methods to calculate the kinetic/mechanical energy of rigid body - or a ragdoll. - + Improved thread safety. - + Added command-line options to DacWizard: --openGL3, --forceDialog, --verbose - + Added a `getAngularVelocityLocal()` method to `PhysicsRigidBody`. - + Moved the `FilterAll` class from MinieExamples into the library. - + Added `getFrameTransform()` methods for cone, hinge, 6dof, and slider joints. - + Updated the native libraries to version 1.0.50 of `Libbulletjme`. - + Built using Gradle v5.3.1 . - + Based on version 2.26 of the `jme3-utilities-heart` library. - -## Version 0.8.0 released on 15 April 2019 - - + Made IK joints aware of ragdoll mode. (API changes) - + Privatized the `PhysicsSpace.physicsSpaceId` field. (API change) - + Privatized all fields in `VehicleTuning`. (API change) - + Added `getAngles()` and `getPivotOffset()` to the `SixDofJoint` class. - + Publicized `RagUtils.coordsMap()` and added `RagUtils.findSkeletonControl()`. - + Implemented `Comparable`, `equals()`, and `hashcode()` - in the `LinkConfig` class. - + Updated the native libraries to version 1.0.49 of `Libbulletjme`. - + Avoided invoking `PhysicsRigidBody.addJoint()` directly. - + Based on version 2.25 of the `jme3-utilities-heart` library. - + Made progress on applications: - + Added `DacWizard` and `TestIssue1058` applications. - + Added a `Face` interface to each model tuning. - -## Version 0.7.7 released on 28 March 2019 - - + Moved `RagUtils.vertexLocations()` and `VectorSet` to - the `jme3-utilities-heart` library. - + Made progress on applications: - + Added `WatchDemo` app. - + Disabled contact response in `TuneDac`. - + Moved IK controllers to their own package. - + Used `getInverseInertiaWorld()` in `TrackController`. - -## Version 0.7.6 released on 24 March 2019 - - + Fixed a bug where `attachmentMass()` could return an outdated value. - + Improved the performance of volume calculations for hull and multi-sphere - shapes. - + Various API changes in `RagUtils`. - + Added a `MultiSphere` constructor for a 2-sphere shape, based - on a `RectangularSolid`. - + Added `boundingBox()` methods for collision shapes and collision objects. - + Added `countRigidBodies()` and `getPcoList()` methods to the - `PhysicsSpace` class. - + Added a `density()` method to the `PhysicsLink` class. - + Added a `HullCollisionShape` constructor based on a `FloatBuffer`. - + Added a `TwoSphere` heuristic for generating `PhysicsLink` shapes. - + Added a `VectorSet` abstract class with 2 implementations. - + Updated the native libraries to version 1.0.40 of `Libbulletjme`. - -## Version 0.7.5 released on 19 March 2019 - - + Added a `DumpFlags` enum and used it to simplify the API - of `PhysicsDumper`. (API change) - + Added a `DumpFlag` to disable dumping collision objects in physics spaces. - + Added a `getSpatial()` method to the `VehicleControl` class. - + Based on version 2.23 of the `jme3-utilities-heart` library - and JME 3.2.3-stable. - -## Version 0.7.4 released on 12 March 2019 - - + Fixed bugs that caused crashes in `GImpactCollisionShape.read()` and - `GImpactCollisionShape.write()`. - + Made `PhysicsDescriber` and `PhysicsDumper` both cloneable. - + Added a `countHullVertices()` method to the `HullCollisionShape` class. - + Added 2 configuration flags to `PhysicsDumper`. - + Reorganized the `PhysicsDumper` code related to joints. - + Updated the native libraries to version 1.0.37 of `Libbulletjme`. - + Based on version 2.22 of the `jme3-utilities-heart` library. - -## Version 0.7.3 released on 9 March 2019 - -Fixed a bug where `HullCollisionShape.copyHullVertices()` didn't fill the -`result` array. - -## Version 0.7.2 released on 9 March 2019 - -Important changes to the library: - - + Fixed a bug where the angular factors of `PhysicsRigidBody` weren't - cloned correctly. - + Fixed a bug where the inverse inertia of `PhysicsRigidBody` wasn't - read/written/cloned. - + Write the platform type during `MeshCollisionShape` save and compare - during load, since saved BVH may be incompatible between platforms. - + Added a `getInverseInertiaWorld()` method to the `PhysicsRigidBody` class. - + Added a `copyHullVertices()` method to the `HullCollisionShape` class. - + Simplified `PhysicsDumper` output. - + Updated the native libraries to version 1.0.37 of `Libbulletjme`. - -## Version 0.7.1 released on 4 March 2019 - -Important changes to the library: - - + Fixed read/write/clone bugs in `MeshCollisionShape`. - + Fixed JME issue #1029 using riccardobl's approach. - + Fixed a crash that occurred while loading a rigid body with mass=0. - + Fixed a `NullPointerException` that occurred while dumping - single-ended joints. - + Added `jump(void)` and `setGravity(float)` methods to `PhysicsCharacter`. - + Changed the default gravity direction for a `PhysicsCharacter` from -Z to -Y. - + Moved the `MyObject.describeUser()` method to the `PhysicsDescriber` class. - + Publicized the `BulletAppState.stopPhysics()` method for better compatibility - with `jme3-bullet`. - + Added 2 new constructors for `BoxCollisionShape`. - + Dump additional information on rigid bodies in `PhysicsDumper`. - + Ensure that translation axes are locked in `RangeOfMotion`. - + Reorganized the location/orientation getters for collision objects. - + Updated the native libraries to version 1.0.34 of `Libbulletjme`. - -## Version 0.7.0 released on 19 February 2019 - -Important changes to the library: - - + Changed the semantics of `DacConfiguration.detach()`: from unlinking - a `Bone` to detaching an attachment. (API change) - + Removed the `unlinkAttachment()` method - from the `DacConfiguration` class. (API change) - + Replaced `CompoundCollisionShape.getChildren()` with a new `listChildren()` - method. (API change) - + Fixed a bug where adding a `DynamicAnimControl` - to a `Geometry` caused a crash. - + Fixed a bug where an attached model didn't follow the rigid body when - its `AttachmentLink` was in dynamic mode. - + Fixed a bug where `MultiSphere.read()` threw a `ClassCastException`. - + Created a `MinieCharacterControl` class as a replacement - for JME's `CharacterControl`. - + Added a `pinToSelf()` method to the `DynamicAnimControl` class. - + Added a `contains()` method to the `PhysicsSpace` class. - + Added `findIndex()` and `listChildren()` methods to the - `CompoundCollisionShape` class - -## Version 0.6.5 released on 8 February 2019 - -Important changes to the library: - - + Fixed various read/write bugs in `BetterCharacterControl`. - + Cloned the rigid body in `BetterCharacterControl.cloneFields()`. - + Standardized `BetterCharacterControl` getters to use - caller-provided storage. (API changes) - + Renamed the `describe()`, `getAxis()`, and `parseShapeId()` methods in the - `MyShape` class. (API changes) - + Added a `getRigidBody()` method to the `BetterCharacterControl` class. - + Added accessors for 7 parameters (anisotropic friction, rolling friction, - spinning friction, contact damping, contact stiffness, deactivation time, - and contact processing threshold) to the `PhysicsCollisionObject` class. - These should affect only rigid bodies and vehicles. - + Added a `parseId()` method to the `MyObject` class. - + Implemented the `Comparable` interface for the `CollisionShape` class. - + Added a `setLocationAndBasis()` method to the `PhysicsCollisionObject` class. - + Added accessors for friction and restitution to the `PhysicsGhostObject` - and `PhysicsCharacter` classes. These should have no effect. - + Updated the native libraries to version 1.0.30 of `Libbulletjme`. - -## Version 0.6.4 released on 25 January 2019 - -Important changes to the library: - - + Standardized the `PhysicsCharacter.getWalkDirection()` method. (API change) - + Fixed a bug where debug shapes were re-used incorrectly. - + Fixed a bug where 6 `PhysicsCharacter` parameters were neither loaded - nor saved. - + Added an `isDynamic()` method to the `PhysicsRigidBody` class. - + Added optional axes to debug visualizations. - + Moved the CCD accessors to the `PhysicsCollisionObject` class. - + Added a `copyCenter()` method to the `MultiSphere` class. - + Added the capability to disable the startup message. - + Added `getUpDirection()` and `reset()` methods to the - `PhysicsCharacter` class. - + Added the capability to configure the `PhysicsCharacter` sweep test. - + Reduced `CollisionShape` validation in the `PhysicsRigidBody` class. - + Updated the native libraries to version 1.0.29 of `Libbulletjme`. - -Important changes to the examples: - - + Port the `TestQ3` app from `jme3-examples`. - + In `TestRectagularSolid`, set the seed for each trial and add UI text. - -## Version 0.6.3 released on 17 January 2019 - -Important changes to the library: - - + Prohibited `PhysicsRigidBody.setKinematic()` on static bodies. - + Used `EmptyShape` to permit linking a bone without - vertices in a `DynamicAnimControl`. - + Added `getSpatial()` methods to `GhostControl` and `RigidBodyControl`. - + Updated the native libraries to version 1.0.24 of `jme3-bullet-native`. - -Important changes to the examples: - - + Added new apps: `HelloDac`, `HelloBoneLink`, `TestHullContact`. - + Added apps from the jme3-examples (sub)project: `TestSimplePhysics`, - `TestRagdollCharacter`, and `TestBoneRagdoll`. - + Added example tuning for CesiumMan model. (model not provided) - + Simplified the example tuning for the Jaime model. - -## Version 0.6.2 released on 6 January 2019 - - + Fixed bug where `SimpleSolidControl.onAdd()` threw a `NullPointerException` - if the control wasn't added to a `PhysicsSpace`. - + Added a `countJoints()` method to `PhysicsRigidBody`. - + Added a `setLimit()` method to `HingeJoint`. - + Allowed vertical translation of heightfields. - + Based on version 2.18 of the `jme3-utilities-heart` library - and JME 3.2.2-stable. - -## Version 0.6.1 released on 28 December 2018 - - + Added an option to calculate local coordinates - in `DynamicAnimControl.findManagerForVertex()`. - + Added a `chainLength` argument to `DynamicAnimControl.setDynamicChain()`. - + Finalized 4 library methods. - + Created a `CameraOrbitAppState` class for use in examples. - + Based on version 2.17 of the `jme3-utilities-heart` library - and JME 3.2.2-beta1. - + Disable scene-graph culling for animated models in examples. - -## Version 0.6.0 released on 15 December 2018 - -Noteworthy additions: - - + An `IKController` class for inverse kinematics. Each `PhysicsLink` maintains - a list of IK controllers. - + 3 IK joint creation methods in `DynamicAnimControl`: - `moveToBody()`, `moveToWorld()`, and `pinToWorld()`. - + Each `DynamicAnimControl` keeps a list of IK joints and disables those - joints when entering ragdoll mode. - + A `BalanceDemo` with 2 examples of `IKController`. - + An `EmptyShape` class. - + A `setDynamicChain()` method in `DynamicAnimControl`. - + Optional `Biped` and `Binocular` interfaces for `DynamicAnimControl` - subclasses. - + A `footprint()` method to calculate the "footprint" of a `PhysicsLink`. - + An `animateSubtree()` method for `DynamicAnimControl`. - + A constructor for a single-ended `Point2PointJoint` with its - constraint already satisfied. - + An `isActive()` method for all collision objects (not just rigid bodies). - + An `isDetached()` method for all physics links (not just attachments). - + A `setContactResponse()` method for physics characters (not just bodies). - + Simple `compareTo()`, `equals()`, and `hash()` methods for collision objects. - -Bugs fixed: - - + Single-ended point-to-point joints were created with incorrect world - locations for their pivots. - + `PhysicsRigidBody` and `PhysicsCharacter` were not cloned properly. - -Debugging improvements: - - + Generally made dumps more compact by trimming trailing zeros. - + In dumps, indicate joints with out-of-space bodies. - + In dumps, indicate joints that lack a dynamic body. - + In dumps, indicate non-responsive rigid bodies. - + In visualizations, draw non-contact physics characters in yellow wireframe. - -Other important changes: - - + Added the concept of a `DynamicAnimControl` being "ready" for dynamic-mode - only after the 1st physics timestep. This helps avert initialization bugs. - + Turned off hardware skinning in `DacLinks.createSpatialData()` to provide - access to the true positions of mesh vertices. - + Modified the `DynamicAnimControl.centerOfMass()` method to also estimate the - velocity vector of the center of mass. - + Links in kinematic mode now update their body's location and velocity for - every frame, instead of just for each timestep. - + Eliminated the `PhysicsJoint.getPivotInWorld()` method. (API change) - + Renamed `DacPhysicsLinks` to `DacLinks`. (API change) - + `PhysicsSpace` accessors now return physics-object collections sorted by ID. - -Other details: - - + Updated the native libraries to version 1.0.21 of `jme3-bullet-native`. - + Based on version 2.16 of the `jme3-utilities-heart` library. - -## Version 0.5.1 released on 5 December 2018 - - + Added a "contact response" option for rigid bodies. - + Added an `isStatic()` method to `PhysicsCollisionObject`. - + Prohibited scaling of `SimplexCollisionShape`. - + Added a `MultiSphere` constructor for a capsule shape with indexed axis. - -Noteworthy changes to debug visualization: - - + Fixed a bug where physics objects and joints continued to be visualized after - setting a filter to exclude them. - + Update debug spatials on every change to `debugMeshNormals` - or `debugMeshResolution`. - + For a `CompoundCollisionShape`, generate a new debug spatial on every frame. - + Visualize non-responsive rigid bodies in yellow. - -Noteworthy changes to `DynamicAnimControl`: - - + Added `centerOfMass()` and `setAttachmentConfig()` methods. - + Collect mesh-vertex coordinates in a `HashSet` (instead of an `ArrayList`) - to increase the efficiency of `createSpatialData()`. - -Other details: - - + Updated the native libraries to version 1.0.20 of `jme3-bullet-native`. - + Based on version 2.15 of the `jme3-utilities-heart` library. - -## Version 0.5.0 released on 29 November 2018 - -Minie moved from the Jme3-utilities Project to a new GitHub repo. - -Noteworthy features added: - - + Added single-ended versions of all 6 `PhysicsJoint` types. - + Added optional heuristics for configuring the center, shape, and - mass of a `PhysicsLink`. - + Added 4 methods for compatibility with the `jme3-bullet` library. - + Cache and re-use debug meshes. - + Added an enable flag and a breaking impulse threshold to every - `PhysicsJoint` object. - + Added an MhGame model for use by `TestDac`. - + Added `SeJointDemo`, `TestLargeMesh` and `TestRectangularSolid` example apps. - + Added a `HullCollisionShape` constructor based on `RectangularSolid`. - + Added `MultiSphere` constructors based on `BoundingSphere` - and `RectangularSolid`. - -Other important changes: - + Debug-mesh properties are now per-collision object, instead of per-shape. - + In the example model tunings, configure masses based on density. - + In `BulletJointDebugControl`, visualize the A and B ends of each joint - in distinct colors. - + Moved 10 assertion-based tests to the library's "test" source set. - + Fixed a bug in the `TestDac` application where controls were not removed - for certain models. - + Fixed a bug in `BulletVehicleDebugControl` where odd-numbered wheels were - never updated. - -Other details: - - + Updated the native libraries to version 1.0.18 of `jme3-bullet-native`. - + Based on version 2.14 of the `jme3-utilities-heart` library. - -## Version 0.4.5 released on 20 November 2018 - -Main features added: - - + New `MultiSphere` collision shape and `MultiSphereDemo` app. - + Configure normals and resolution of the debug mesh for each collision shape. - + Register an init listener for a `BulletDebugAppState`. - + The Puppet model with its licensing history (for the `TestDac` app). - + Test whether a collision shape is convex. - + Vertex counts and volume calculations for various collision shapes. - + Calculate half extents for simplex and hull collision shapes. - + Copy the vertices of a `SimplexCollisionShape`. - -Bugs fixed: - - + Incorrect default limits for `SixDofJoint`. - + Crash in `PhysicsSpace.stepSimulation()` after reading a hull shape - from a model asset. - + Debug mesh is not updated after the shape's margin changes. - + `MyShape.volume()` ignores scaling of capsule shapes. - -Incompatible changes to the library API: - - + Renamed `PhysicsRigidBody.getJoints()` to `listJoints()` and changed its - semantics to reduce aliasing. - + Privatized the joint list in `PhysicsRigidBody`. - + Removed 3 inverse-kinematics stub methods from `DynamicAnimControl`. - + Standardized the `getLowerLimit()` and `getUpperLimit()` methods - in the `TranslationalLimitMotor` class. - + Removed the `getTriangleIndexVertexArray()` method - from the `NativeMeshUtil` class. - -Other details: - - + Updated the native libraries to version 1.0.15 of `jme3-bullet-native`. - + Based on version 2.13 of the `jme3-utilities-heart` library. - -## Version 0.4.4 released on 12 November 2018 - - + Fixed map cloning bugs in `DynamicAnimControl` and - `ConfigDynamicAnimControl`. - + Added a `countJoints()` method and removed the `destroy()` method of - the `PhysicsSpace` class. - + Reduce aliasing in the `BulletDebugAppState` constructor. - -## Version 0.4.3 released on 8 November 2018 - - + Changed `DynamicAnimControl.setMass()` to take a bone name or a physics link. - + Made `MyControlP` aware that `DynamicAnimControl` does not support - local physics. - + Added `DynamicAnimControl` tunings for the MhGame and Puppet models. - -## Version 0.4.2 released on 3 November 2018 - -More changes to `DynamicAnimControl`: - - + Added the capability to release attachments. - + Gave each `PhysicsLink` a name that's distinct from its bone's name. - + Added `hasAttachmentLink()` and `unlinkAttachment()` methods. - + Removed the `isLinkName()` method. - + Renamed the `isBoneLinkName()` method to `hasBoneLink()`. - + Renamed the `unlink()` method to `unlinkBone()`. - + Changed the `attachmentBoneNames()` and `linkedBoneNames()` methods to return - arrays instead of collections. - + Added example tunings for the Ninja and Oto models. - + Detect and reject models with ignoreTransform geometries. - -Other noteworthy changes to Minie: - - + In `RangeOfMotion`, set the joint's angular limits in addition - to its motor limits. - + In `SixDofJoint`, store rotational motors in an array, not a linked list. - -## Version 0.4.1 released on 1 November 2018 - -More design and implementation changes to `DynamicAnimControl`: - - + Added support for attachments nodes. - + The center of a linked rigid body can be offset from its joint. - + Major refactoring to base `AttachmentLink`, `BoneLink`, and `TorsoLink` - on a new `PhysicsLink` class. - + Renamed `JointPreset` class to `RangeOfMotion`. - + Refer to links by reference instead of by name. - + Added a `forceKinematic` option to the `freeze()` methods. - + Renamed many methods. - + Preserve animation data during a `rebuild()`. - + Moved `RagdollCollisionListener` to the `com.jme3.bullet.animation` package. - + Completed the `read()` and `write()` methods. - + Lowered the default for `torsoMass` from 15 to 1. - + Fixed bug where `DynamicAnimControl` reported collisions from other DACs. - + Catch any attempt to set local physics. - + Don't re-order controls unless it's necessary. - -Other noteworthy changes to Minie: - - + Improved dumps and descriptions of joints, physics controls, - collision objects, and rigid bodies. - + Standardized `getPivot()` methods to avoid aliasing. - + Avoided aliasing in `setViewPorts()` methods. - + Added a `getTargetVelocity()` method to `TranslationalLimitMotor`. - + Added an `activate()` method to `PhysicsCollisionObject`. - + Fixed a bug that caused an assertion failure while reading - a `CompoundCollisionShape`. - + Updated the native libraries to v1.0.12 of `jme3-bullet-native`. - -## Version 0.4.0 released on 20 October 2018 - -Extensive design and implementation changes to `KinematicRagdollControl` -and its ilk, now in its own `com.jme3.bullet.animation` package. -`DynamicAnimControl` is now the core, with `ConfigDynamicAnimControl` for -configuration. - - + Bone shapes are now aligned with bone coordinate axes instead of mesh - coordinate axes. - + The inverse-kinematics code has been removed in anticipation of a - complete redesign. - + Kinematic mode now has 4 submodes. - + Rigid body updates now take place just before each physics tick instead of - during scene-graph updates. - + Added an interim tool for tuning a `DynamicAnimationControl`. - + Added example tunings for the Jaime and Elephant models. - + Added per-axis freezing of dynamic joints. - -Other noteworthy changes to Minie: - - + Added a getSpatial() method to the `AbstractPhysicsControl` class and - privatized its `spatial` field. Also added an empty controlRender() method. - + Bugfix: `NullPointerException` in `PhysicsSpace.getGravity()`. - + Bugfix: JVM crashed while reading a `SixDofJoint` from a J3O asset. - + Added check for invalid location in `RigidBodyMotionState.getWorldLocation()` - + Disabled the `isInWorld` checks in `PhysicsRigidBody`. - + Renamed and standardized the accessors of `RigidBodyMotionState`. - + In `RotationalLimitMotor`: renamed the limit/bounce accessors, added - accessors for CFM parameters, added `getAccumulatedImpulse()` and - `getAngle()` methods. - + In `TranslationalLimitMotor`: added accessors for 4 parameters, added - `getOffset()` and `setTargetVelocity()` methods. - + Added `getPhysicsTransform()` and `setPhysicsTransform()` methods to - the `PhysicsRigidBody` class. - + Bypassed `setSpatial()` in `GhostControl` and `RigidBodyControl` in case the - `Spatial` does not change. - + Eliminated unnecessary aliasing in joint constructors. - + Named the debug textures in `BulletDebugAppState`. - + Visualize kinematic bodies in blue instead of magenta, to distinguish - them from dynamic bodies. - -## Version 0.3.5 released on 10 October 2018 - -Enhancements to `KinematicRagdollControl`: - - + Began treating the torso more like a bone. - + Implemented a new algorithm to construct hulls without weight thresholds. - + Redesigned how mass is configured and totaled. - + Lowered the default dispatch threshold from 10 to 0. - + Moved all the code in `RagdollUtils` to other classes. - + Include the torso in `setDamping()`. - + Added `boneMass()`, `getBoneLink()`, `getJointPreset()`, `gravity()`, - `linkedBoneNames()`, `setGravity()`, and `torsoMass()` methods. - -Other noteworthy changes: - - + Added `getPhysicsScale()` and `setPhysicsScale()` methods to - `PhysicsRigidBody`. - + Removed the `space` argument from the addPhysics() and removePhysics() - methods of `AbstractPhysicsControl` and its subclasses. - + Added a list-based constructor for `HullCollisionShape`. - + Fixed a logic bug in `MyObject` where vehicles were not recognized. - + Added a `setPivot` method to `SixDofJoint.setPivot()`. - + Added a `physicsTransform()` method to `RigidBodyMotionState`. - + Added `JointEnd` and `TestRagdollScaling` classes. - + Updated shared libraries to v1.0.7 of `jme3-bullet-native`. - + Removed the unused `PhysicsSpace.initNativePhysics()` method. - -## Version 0.3.4 released on 5 October 2018 - -Enhancements to `KinematicRagdollControl`: - - + Removed the `weightThreshold = -1` hack. - + Replaced `boneList` and `RagdollPreset` with a joint map. - + Changed coordinate translation to utilize animated geometries - instead of the controlled spatial. - + Eliminated the temporary removal of the controlled spatial from the scene. - + Changed to continue updating ragdoll even after the controlled spatial moves. - -Other noteworthy changes: - - + Fixed 2 logic errors in `CylinderCollisionShape.canScale()`. - + Added result validation to `PhysicsRigidBody.getPhysicsLocation()`. - + Fixed JME issue #931. - + Updated shared libraries to v1.0.5 of `jme3-bullet-native`. - + Improved `applyScale` option in `GhostControl` and `RigidBodyControl` so that - it will fall back to uniform scaling (if necessary) or skip rescale - (if scale is unchanged). - + Added an `isEmpty()` method to the `PhysicsSpace` class. - + Added `TestSetScale`, `TestIssue918`, and `TestIssue919`. - -## Version 0.3.3 released on 2 October 2018 - - + Added `applyScale` option to `RigidBodyControl` and `GhostControl`. - + Added default margin for collision shapes other than capsule and sphere. - + Eliminated runtime dependency on JME's `jme3-bullet-native` library. - + Removed `TestIssue896`. - -## Version 0.3.2 released on 28 September 2018 - - + Made many classes `JmeCloneable`, especially physics controls. - + Added custom debug materials to collision objects. - + Added `canScale()` method to collision shapes. - + Worked around JME issue #919. - + Prevented setting the margin of a capsule/sphere shape. - + Implemented limb damping in `KinematicRagdollControl`. - + Added `getTorso()` method to `KinematicRagdollControl`. - + Added check for rotation/translation of a heightfield rigid body. - + Converted `PhysicsBoneLink` to a standalone class. - + Removed unnecessary constructor from CollisionShape. - + Added tests. - -## Version 0.3.1 released on 24 September 2018 - - + Fixed JME issue #896 and added a test for it. - + Disabled `getMargin()` and `setMargin()` for capsule and sphere shapes. - + Initialized the scale and margin of compound shapes. - + Removed various methods and arguments. - + Added `TestSetMargin` to the test project. - -## Version 0.3.0 released on 23 September 2018 - - + Fixed JME issue #740. - + Standardized the design of constructors and accessors to reduce aliasing - of vectors and quaternions and enable the use of caller-allocated storage. - + Implemented a more practical approach to filtering debug objects. - + Simplified `PhysicsCollisionEvent` by eliminating event types. - + Renamed 2 `PhysicsJoint` methods that misspelled "bodies". - + Removed many needless fields, methods, and constructors. - + Made the `VehicleTuning` class `JmeCloneable` and `Savable`. - + Addressed the possibility of multiple physics controls added to the - same Spatial. - + Replaced 6 parameters of `VehicleWheel` with a `VehicleTuning` reference. - + Eviscerated 5 `cloneForSpatial()` methods. - + Based on version 2.10 of the jme3-utilities-heart library. - -## Version 0.2.10 released on 12 September 2018 - - + Fixed JME issue #898. - + Require collision margin > 0 . - + Changed default collision margin from 0 to 0.04 . - + Disabled setMargin() for SphereCollisionShape. - + Don't allow dynamic bodies to have heightfield or plane shapes. - + Publicized loggers. - + Added massForStatic constant in PhysicsRigidBody. - + Added 2 tests. - + Privatized the HeightfieldCollisionShape.createShape() method. - -## Version 0.2.9 released on 9 September 2018 - - + Removed PhysicsCollisionEventFactory. - + Removed HeightfieldCollisionShape.createJmeMesh(), - VehicleWheel.getGroundObject(), and a constructor for PhysicsGhostObject. - + Privatized various methods. - + Fixed JME issue #894. - + Implemented a cleaner fix for JME issue #889. - + Deal with scale changes in physics-debug controls. - + Decided that physics-debug controls should implement neither JmeCloneable - nor Savable. - + Added validation of method arguments. - + Finalized various fields. - + Created the jme3utilities.minie.test package. - -## Version 0.2.8 released on 3 September 2018 - - + Removed some unnecessary methods. - + Reduced the scope of many methods. - + Renamed getMass() to mass() in MyControlP. - + Fixed JME issue #889. - + Added validation of method arguments, plus some assertions. - + Based on version 2.8 of the jme3-utilities-heart library. - -## Version 0.2.7 released on 1 September 2018 - - + Don't setLocalScale() on spatials controlled by debug controls; this is - related to JME issue #887. - + Handle ignoreTransforms in GhostControl and RigidBodyControl. - + Describe rigid bodies and RigidBodyControls similarly. - + Describe shape scaling and spatial scaling similarly. - + Describe the half extents of box shapes. - -## Version 0.2.6 released on 31 August 2018 - - + Fixed JME issues 883 and 887. - + Ensured that debugViewPorts[] gets initialized in BulletAppState. - + Changed AbstractPhysicsControl to handle ignoreTransform. - + Changed DebugAppStateFilter interface to consider only Savable objects. - + Added validation of method arguments, plus some assertions. - + Reduced the scope of many fields and methods. - + Finalized some fields. - + Removed some unused fields and methods. - + Added jme3-bullet-native runtime dependency to POM. - + Replaced iterators with enhanced loops (for readability). - + Standardized logging. - -## Version 0.2.5 released on 24 August 2018 - - + Bugfix: PhysicsDumper prints incorrect number of vehicles. - + Bugfix for JME issue #867 contributed by Riccardo Balbo. - + Privatized numerous protected fields. - + Removed 3 PhysicsSpace constructors. - + Enhanced PhysicsDumper to handle app states and print the joint list and - (non-identity) orientation for each rigid body. - + Added BulletAppState.getBroadphaseType(). - + Added validation of method arguments. - + Changed BulletAppState.setWorldMax() and .setWorldMin() to avoid aliasing. - -## Version 0.2.4 released on 22 August 2018 - - + Renamed MinieVersion.getVersionShort() to versionShort(). - + Used MyAsset to create debug materials. - + In BulletDebugAppState, only render viewports that are enabled. - + Based on version 2.7 of the jme3-utilities-heart library. - -## Version 0.2.3 released on 17 August 2018 - -+ Renamed ray-test flag accessors in PhysicsSpace class. (API change) -+ Added maxSubSteps() method to the PhysicsSpace class. -+ Include more detail when dumping a physics space. -+ Based on version 2.6 of the jme3-utilities-heart library. - -## Version 0.2.2 released on 24 July 2018 - -+ Enhanced PhysicsDescriber to describe axes of cone shapes. -+ Based on version 2.5 of the jme3-utilities-heart library. -+ Remove an obsolete TODO comment. - -## Version 0.2.1 released on 19 February 2018 - -+ Changed BulletDebugAppState to accept an array of viewports to add scenes to, - instead of creating its own viewport. -+ Added getAxis() method to the ConeCollisionShape class. -+ Allow uniform scaling of capsule, cylinder, and sphere shapes. - -## Version 0.2.0 released on 2 February 2018 - -+ Added axisIndex(), describe(), describeType(), halfExtents(), height(), - radius(), setHalfExtents(), setHeight(), and setRadius() methods to the - MyShape utility class. -+ Copied source files from jme3-bullet library and corrected many minor issues. - -## Version 0.1.2 released on 26 January 2018 - -This was the initial baseline release, based largely on code formerly -included in the jme3-utilities-heart, jme3-utilities-debug, and -jme3-utilities-x libraries. +# Release log for the Minie library, DacWizard, and MinieExamples + +release log continues at https://github.com/stephengold/Minie/blob/master/MinieLibrary/release-notes.md + +## Version 0.9.15for33 released on 29 August 2019 + + + API changes: + + Deprecated the `extrapolateTransform()` and `getPhysicsScale()` methods of + the `PhysicsRigidBody` class. + + Deleted the unused `OverlapListener` interface. + + Fixed bug: + + "body A does not exist" `NullPointerException` while loading a + `DynamicAnimControl` from a J3O. + + Added library features: + + `getScale()` and `getTransform()` methods for `PhysicsCollisionObject` + + Argument validation for `MeshCollisionShape` constructors + + Other improvements: + + Merged the functionality of `TestHullContact` into the + `MultiSphereDemo` application. + + Stopped overriding the default collision margin in `MultiSphereDemo`. + +## Version 0.9.14for33 released on 25 August 2019 + + + Simplified the construction of collision shapes from multiple meshes. + + Changed `DacLinks` to skip `setGravity()` on kinematic bodies. + + Added verification of the local copy of gravity in `PhysicsSpace` + when assertions are enabled. + + Updated the native libraries to version 2.0.7 of `Libbulletjme`. + + Based on version 3.0 of the `jme3-utilities-heart` library, version + 0.7.7 of the `jme3-utilities-ui` library, and version 0.9.9 of the + `jme3-utilities-nifty` library. + +## Version 0.9.13for33 released on 7 August 2019 + + + API changes: + + Finalized the `TorsoLink.countManaged()` method. + + Standardized `TranslationalLimitMotor.getAccumulatedImpulse()` to use + caller-provided storage. + + Fixed bugs: + + Various bugs in debug visualization, including one where shadows + were cast by visualizations of bounding boxes and swept spheres and one + where axes were visualized after shapes were no + longer visualized. + + A `NullPointerException` in `DacLinks.findManagerForVertex()`. + + A `NullPointerException` in `DacLinks.managerMap()`. + + An `AssertionError` in `PhysicsSpace.countJoints()`. + + An `AssertionError` caused by scaled compound shapes. + + User objects were not cloned/serialized, even if they + implement `JmeCloneable` or `Savable`. + + Motor-enable flags and accumulated impulses weren't properly + loaded/saved/cloned. + + Control not found in `TrackDemo`. + + A `NullPointerException` when changing models in `BalanceDemo`. + + Added library features: + + Support for V-HACD using Riccardo's Java bindings. + + A warning in case a joint is added to a `PhysicsSpace` before + the bodies that it joins. + + Constructors for box/cylinder/sphere shapes based on float buffers. + + Optional filtering of physics dumps. + + Dump CCD/sleep parameters of dynamic rigid bodies. + + `isEnabled()` and `setEnabled()` methods for `TranslationalLimitMotor`. + + A flag to dump motors. + + A `setAccumulatedImpulse()` method for `RotationalLimitMotor`. + + `FilterAll` methods `countExceptions()`, `defaultReturnValue()`, + and `listExceptions()`. + + A `UserFilter` class. + + Other improvements: + + Extended the `setDebugViewPorts()` method of `BulletAppState` to accept + multiple arguments. + + Added a `JointDemo` app. + + More thorough dumps/descriptions of joints, especially 6-DOF joints + and their motors. + + Enhanced the `BuoyDemo`, `MultiSphereDemo`, `RopeDemo`, `TestDac`, + and `TestSoftBody` apps with hotkeys to toggle debug visualization options. + + Added a hotkey to `MultiSphereDemo` to delete gems. + + Updated the native libraries to version 2.0.5 of `Libbulletjme`. + + Based on version 2.31 of the `jme3-utilities-heart` library, version + 0.7.6 of the `jme3-utilities-ui` library, and version 0.9.8 of the + `jme3-utilities-nifty` library. + +## Version 0.9.8for33 released on 17 July 2019 + + + Added an `update(float, int)` method to the `PhysicsSpace` class. + + Added `clearCache()`, `getDebugMesh()`, and `getDebugShape(CollisionShape)` + methods to the `DebugShapeFactory` class. + + Extended the constructors for `CompoundMesh`, `GImpactCollisionShape`, and + `HullCollisionShape` to accept multiple meshes. + + Updated the `TestHullContact` app to be more like a demo. + + Built using Gradle v5.5.1 . + +## Version 0.9.6for33 released on 13 July 2019 + + + Finalized the `getRigidBody()` method in the `PhysicsLink` + class. (API change) + + Allowed soft-body nodes to have mass=0 (for pinning). + + Changed the semantics of `RigidBodyControl.setKinematicSpatial()` + to match jme3-bullet. + + Added tutorial apps: `HelloSoftBody`, `HelloSoftSoft`, `HelloCloth`, + and `HelloSoftRope`. + + Added a `DividedLine` mesh class. + + Moved the `massForStatic` constant to the `PhysicsBody` class. + +## Version 0.9.5for33 released on 6 July 2019 + + + Modified `DynamicAnimControl` to work with armatures as well as skeletons. + + Fixed JME issue 1135 (`ConeJoint` causes rigid body to disappear). + + Fixed cloning bugs in `SoftPhysicsJoint` and `SoftBodyControl`. + + Removed 3 `jme3test` apps that now work unmodified with Minie. + + Added a `contains(PhysicsJoint)` method to the `PhysicsSpace` class. + + Added modified `TestRagDoll` and `TestGimpactShape` apps that + work with Minie. + + Added hotkey-binding hints to `TestHeightfield` and `TestHullContact`. + + Added a `TestStaticBody` test. + + Updated the native libraries to version 1.0.90 of `Libbulletjme`. + + Based on version 2.29 of the `jme3-utilities-heart` library. + + Based on version 3.3.0-alpha2 of jMonkeyEngine. + +## Version 0.9.4 released on 2 July 2019 + + + API changes: + + Made the `DebugAppStateFilter` interface compatible with jme3-bullet again. + + De-publicized the `PhysicsSpace.setLocalThreadPhysicsSpace()` method. + + Removed the `createTriangleIndexVertexArray()` method from the + `NativeMeshUtil` class. + + Re-implemented anchors as a kind of `PhysicsJoint`. + + Renamed the `BulletJointDebugControl` class. + + Removed the `updateAnchorMesh()` method from the + `NativeSoftBodyUtil` class. + + Fixed bugs: + + JME issue 1120 (scaled `GImpactCollisionShape` gets incorrect bounding box) + + bounding box of a shape not updated for `getAabb()` + + JME issue 1125 (inaccurate visualization of `HeightfieldCollisionShape`) + + clusters/joints/nodes of a soft body not cloned/serialized properly + + tau and impulseClamp of a `Point2PointJoint` not de-serialized properly + + 7 limit-motor parameters of a `SixDofJoint` not serialized properly + + JME issues 1126 and 1127 (`TestHoverTank` crash and reset action) + + Added library features: + + new classes `SoftAngularJoint` and `SoftLinearJoint` for soft-body joints + + new classes `CompoundMesh` and `IndexedMesh` for native meshes + + select single-sided/double-sided debug visualization materials for each + collision object + + an exception list for each `FilterAll` instance + + control which soft bodies have their clusters visualized + + a new constructor for a `HeightfieldCollisionShape` with additional options + + access the `BulletDebugAppState` associated with a `BulletAppState` + + access the `feedback` flag of each physics constraint + + access 6 per-cluster parameters + + access the per-constraint property that overrides the number of + solver iterations + + Other improvements: + + Added hotkey-binding hints to demo apps: press H to toggle hints. + + Bound the up/down arrow keys to control camera movement in + demo applications. + + Added a test for default values of newly created physics objects. + + Customized the `toString()` methods of the `CollisionShape`, + `PhysicsJoint`, and `PhysicsSpace` classes. + + Improved the output of `PhysicsDumper`. + + Customized the `equals()` and `hashCode()` methods of the + `CollisionShape` class. + + Avoided calling native code to fill zero-length buffers. + + Removed native libraries from the Git repository; download them + from GitHub instead. + + Updated the native libraries to version 1.0.89 of `Libbulletjme`. + +## Version 0.9.3 released on 11 June 2019 + + + Moved 2 tutorial apps to a new `jme3utilities.tutorial` package. + + Fixed bugs: + + Spatial transform not applied to static bodies in `RigidBodyControl`. + + `IllegalArgumentException` thrown when translating/rotating a + heightfield-shaped rigid body. + + Some physics controls ignore `isEnabled()`. + + After de-serializing a physics control, its `userObject` is null. + + `TestHeightfield` attached physics control to the root node. + + `TestHeightfield` used wrong logger. + + Added a `PhysicsSpace.destroy()` method for compatibility with jme3-bullet. + + Improved physics dumps. + + Removed uses of shared mutable "constants". + + Made various improvements to MinieExamples. + +## Version 0.9.2 released on 7 June 2019 + + + API changes: + + Privatized 5 fields in the `GhostControl` class. + + Removed the `rebuildSoftBody()` method from the `PhysicsSoftBody` class. + + Added a `Transform` argument to the `updateMesh()` method in the + `NativeSoftBodyUtil` class. + + New features for soft-body physics: + + Added a `SoftBodyControl` class. + + Allowed setting `maxSubSteps` to 0 for a variable-length time step. + + Added `maxTimeStep` parameter to `PhysicsSpace`, for use with a + variable-length time step. + + Added a warning when `setGravity()` is applied to a body that isn't + in any space. + + Publicized the `distributeEvents()` method of `PhysicsSpace`, for use in + non-`BulletAppState` applications. + + An `isEmpty()` method for `PhysicsSoftBody`. + + Built for compatibility with Java 7. + + Added debug visualization of soft-body anchors. + + Avoided cloning/serializing world info and gravity: adding to a physics space + would trash these data. + + Improvements to `DacWizard` and examples: + + Added a `TestSoftBodyControl` application. + + Made `ClothGrid` dynamic and added a `reposition()` method. + + Changed `ClothGrid` to minimize directional bias. + + Added Sony Duck model with license. + + Use LWJGL v3 to allow fullscreen mode on Linux systems (JME issue #947). + + Various improvements to physics dumps. + + Updated the native libraries to version 1.0.73 of `Libbulletjme`. + + Based on version 2.28.1 of the `jme3-utilities-heart` library. + +## Version 0.9.1 released on 28 May 2019 + + + API changes: + + Privatized the `motionState` field in the `PhysicsRigidBody` class. + + Removed methods from the `PhysicsSoftBody` class: + + `addAeroForceToNode()` + + `getPhysicsTransform()` + + `setPhysicsRotation()` + + `setPhysicsTransform()` + + Renamed the `PhysicsRigidBody.getPhysicsTransform()` method + to `extrapolateTransform()`. + + Moved the `SoftBodyWorldInfo` class to the `com.jme3.bullet` package. + + Converted the `PhysicsSoftBody.Config` class to an external class. + + Expanded soft-body physics: got aerodynamics and anchors working. + + Improved debug visualization of soft bodies: + + override default material for shapes if requested + + don't visualize links if the body has faces + + visualize clusters + + generate/update normals if requested + + Other changes to debug visualization: + + Ensured that static rigid bodies are visualized in blue. + + Changed the wireframe debug materials from single-sided to double-sided. + + Added a `debugMeshInitListener` option to add texture coordinates. + + Added a `MinieAssets` sub-project. + + Overrode the default `toString()` method for collision objects. + + Added methods to the `PhysicsSoftBody` class: + + `copyClusterMasses()` + + `countNodesInCluster()` + + `listNodesInCluster()` + + `setWindVelocity()` + + `windVelocity()` + + Added `Aero` and `ClothGrid` classes. + + Updated the native libraries to version 1.0.70 of `Libbulletjme`. + + Based on version 2.28 of the `jme3-utilities-heart` library. + +## Version 0.9.0 released on 14 May 2019 + + + Added a minimal implementation of soft-body physics (based on Dokthar's + prior work) that included `ConfigFlag`, `Icosphere`, `MeshEdge`, + `NativeSoftBodyUtil`, `NetGrid`, `PhysicsBody`, `PhysicsSoftBody`, + `PhysicsSoftSpace`, `RayTestFlag`, `Sbcp`, `SoftBodyWorldInfo`, + `SoftPhysicsAppState`, `SoftDebugAppState`, `SoftBodyDebugControl`, and + `TestSoftBody`. + + Re-publicized the `update()` method and finalized the `getSpaceId()` method + of `PhysicsSpace`. + + Moved the `isInWorld()` method from `PhysicsRigidBody` + to `PhysicsCollisionObject`. + + Moved `TubeTreeMesh` class to the `jme3utilities.minie.test.mesh` package. + + Used `BinaryExporter.saveAndLoad()` to simplify load/save testing. + + Updated the native libraries to version 1.0.61 of `Libbulletjme`. + + Based on version 2.27 of the `jme3-utilities-heart` library. + +## Version 0.8.1 released on 28 April 2019 + + + API changes: + + Privatized 2 protected fields in the `BulletDebugAppState` class. + + Privatized 7 protected fields in the `RigidBodyControl` class. + + Privatized 4 protected fields in the `VehicleControl` class. + + Renamed the `MyObject` class to `MyPco`. + + Removed the `BubbleControl`, `SimpleGhostControl`, + and `SimpleSolidControl` classes. + + De-publicized the `BoundingBoxDebugControl` constructor. + + Removed the `setPivot()` method from the `SixDofJoint` class. + + Changed the semantics of the `addAll()` and `removeAll()` methods in the + `PhysicsSpace` class; they no longer attempt to add/remove physics joints. + + Added a swept-sphere visualization feature. + + Added assertions to catch attempts to read the angular/linear velocities + of non-dynamic rigid bodies. + + Added methods to calculate the kinetic/mechanical energy of rigid body + or a ragdoll. + + Improved thread safety. + + Added command-line options to DacWizard: --openGL3, --forceDialog, --verbose + + Added a `getAngularVelocityLocal()` method to `PhysicsRigidBody`. + + Moved the `FilterAll` class from MinieExamples into the library. + + Added `getFrameTransform()` methods for cone, hinge, 6dof, and slider joints. + + Updated the native libraries to version 1.0.50 of `Libbulletjme`. + + Built using Gradle v5.3.1 . + + Based on version 2.26 of the `jme3-utilities-heart` library. + +## Version 0.8.0 released on 15 April 2019 + + + Made IK joints aware of ragdoll mode. (API changes) + + Privatized the `PhysicsSpace.physicsSpaceId` field. (API change) + + Privatized all fields in `VehicleTuning`. (API change) + + Added `getAngles()` and `getPivotOffset()` to the `SixDofJoint` class. + + Publicized `RagUtils.coordsMap()` and added `RagUtils.findSkeletonControl()`. + + Implemented `Comparable`, `equals()`, and `hashcode()` + in the `LinkConfig` class. + + Updated the native libraries to version 1.0.49 of `Libbulletjme`. + + Avoided invoking `PhysicsRigidBody.addJoint()` directly. + + Based on version 2.25 of the `jme3-utilities-heart` library. + + Made progress on applications: + + Added `DacWizard` and `TestIssue1058` applications. + + Added a `Face` interface to each model tuning. + +## Version 0.7.7 released on 28 March 2019 + + + Moved `RagUtils.vertexLocations()` and `VectorSet` to + the `jme3-utilities-heart` library. + + Made progress on applications: + + Added `WatchDemo` app. + + Disabled contact response in `TuneDac`. + + Moved IK controllers to their own package. + + Used `getInverseInertiaWorld()` in `TrackController`. + +## Version 0.7.6 released on 24 March 2019 + + + Fixed a bug where `attachmentMass()` could return an outdated value. + + Improved the performance of volume calculations for hull and multi-sphere + shapes. + + Various API changes in `RagUtils`. + + Added a `MultiSphere` constructor for a 2-sphere shape, based + on a `RectangularSolid`. + + Added `boundingBox()` methods for collision shapes and collision objects. + + Added `countRigidBodies()` and `getPcoList()` methods to the + `PhysicsSpace` class. + + Added a `density()` method to the `PhysicsLink` class. + + Added a `HullCollisionShape` constructor based on a `FloatBuffer`. + + Added a `TwoSphere` heuristic for generating `PhysicsLink` shapes. + + Added a `VectorSet` abstract class with 2 implementations. + + Updated the native libraries to version 1.0.40 of `Libbulletjme`. + +## Version 0.7.5 released on 19 March 2019 + + + Added a `DumpFlags` enum and used it to simplify the API + of `PhysicsDumper`. (API change) + + Added a `DumpFlag` to disable dumping collision objects in physics spaces. + + Added a `getSpatial()` method to the `VehicleControl` class. + + Based on version 2.23 of the `jme3-utilities-heart` library + and JME 3.2.3-stable. + +## Version 0.7.4 released on 12 March 2019 + + + Fixed bugs that caused crashes in `GImpactCollisionShape.read()` and + `GImpactCollisionShape.write()`. + + Made `PhysicsDescriber` and `PhysicsDumper` both cloneable. + + Added a `countHullVertices()` method to the `HullCollisionShape` class. + + Added 2 configuration flags to `PhysicsDumper`. + + Reorganized the `PhysicsDumper` code related to joints. + + Updated the native libraries to version 1.0.37 of `Libbulletjme`. + + Based on version 2.22 of the `jme3-utilities-heart` library. + +## Version 0.7.3 released on 9 March 2019 + +Fixed a bug where `HullCollisionShape.copyHullVertices()` didn't fill the +`result` array. + +## Version 0.7.2 released on 9 March 2019 + +Important changes to the library: + + + Fixed a bug where the angular factors of `PhysicsRigidBody` weren't + cloned correctly. + + Fixed a bug where the inverse inertia of `PhysicsRigidBody` wasn't + read/written/cloned. + + Write the platform type during `MeshCollisionShape` save and compare + during load, since saved BVH may be incompatible between platforms. + + Added a `getInverseInertiaWorld()` method to the `PhysicsRigidBody` class. + + Added a `copyHullVertices()` method to the `HullCollisionShape` class. + + Simplified `PhysicsDumper` output. + + Updated the native libraries to version 1.0.37 of `Libbulletjme`. + +## Version 0.7.1 released on 4 March 2019 + +Important changes to the library: + + + Fixed read/write/clone bugs in `MeshCollisionShape`. + + Fixed JME issue #1029 using riccardobl's approach. + + Fixed a crash that occurred while loading a rigid body with mass=0. + + Fixed a `NullPointerException` that occurred while dumping + single-ended joints. + + Added `jump(void)` and `setGravity(float)` methods to `PhysicsCharacter`. + + Changed the default gravity direction for a `PhysicsCharacter` from -Z to -Y. + + Moved the `MyObject.describeUser()` method to the `PhysicsDescriber` class. + + Publicized the `BulletAppState.stopPhysics()` method for better compatibility + with `jme3-bullet`. + + Added 2 new constructors for `BoxCollisionShape`. + + Dump additional information on rigid bodies in `PhysicsDumper`. + + Ensure that translation axes are locked in `RangeOfMotion`. + + Reorganized the location/orientation getters for collision objects. + + Updated the native libraries to version 1.0.34 of `Libbulletjme`. + +## Version 0.7.0 released on 19 February 2019 + +Important changes to the library: + + + Changed the semantics of `DacConfiguration.detach()`: from unlinking + a `Bone` to detaching an attachment. (API change) + + Removed the `unlinkAttachment()` method + from the `DacConfiguration` class. (API change) + + Replaced `CompoundCollisionShape.getChildren()` with a new `listChildren()` + method. (API change) + + Fixed a bug where adding a `DynamicAnimControl` + to a `Geometry` caused a crash. + + Fixed a bug where an attached model didn't follow the rigid body when + its `AttachmentLink` was in dynamic mode. + + Fixed a bug where `MultiSphere.read()` threw a `ClassCastException`. + + Created a `MinieCharacterControl` class as a replacement + for JME's `CharacterControl`. + + Added a `pinToSelf()` method to the `DynamicAnimControl` class. + + Added a `contains()` method to the `PhysicsSpace` class. + + Added `findIndex()` and `listChildren()` methods to the + `CompoundCollisionShape` class + +## Version 0.6.5 released on 8 February 2019 + +Important changes to the library: + + + Fixed various read/write bugs in `BetterCharacterControl`. + + Cloned the rigid body in `BetterCharacterControl.cloneFields()`. + + Standardized `BetterCharacterControl` getters to use + caller-provided storage. (API changes) + + Renamed the `describe()`, `getAxis()`, and `parseShapeId()` methods in the + `MyShape` class. (API changes) + + Added a `getRigidBody()` method to the `BetterCharacterControl` class. + + Added accessors for 7 parameters (anisotropic friction, rolling friction, + spinning friction, contact damping, contact stiffness, deactivation time, + and contact processing threshold) to the `PhysicsCollisionObject` class. + These should affect only rigid bodies and vehicles. + + Added a `parseId()` method to the `MyObject` class. + + Implemented the `Comparable` interface for the `CollisionShape` class. + + Added a `setLocationAndBasis()` method to the `PhysicsCollisionObject` class. + + Added accessors for friction and restitution to the `PhysicsGhostObject` + and `PhysicsCharacter` classes. These should have no effect. + + Updated the native libraries to version 1.0.30 of `Libbulletjme`. + +## Version 0.6.4 released on 25 January 2019 + +Important changes to the library: + + + Standardized the `PhysicsCharacter.getWalkDirection()` method. (API change) + + Fixed a bug where debug shapes were re-used incorrectly. + + Fixed a bug where 6 `PhysicsCharacter` parameters were neither loaded + nor saved. + + Added an `isDynamic()` method to the `PhysicsRigidBody` class. + + Added optional axes to debug visualizations. + + Moved the CCD accessors to the `PhysicsCollisionObject` class. + + Added a `copyCenter()` method to the `MultiSphere` class. + + Added the capability to disable the startup message. + + Added `getUpDirection()` and `reset()` methods to the + `PhysicsCharacter` class. + + Added the capability to configure the `PhysicsCharacter` sweep test. + + Reduced `CollisionShape` validation in the `PhysicsRigidBody` class. + + Updated the native libraries to version 1.0.29 of `Libbulletjme`. + +Important changes to the examples: + + + Port the `TestQ3` app from `jme3-examples`. + + In `TestRectagularSolid`, set the seed for each trial and add UI text. + +## Version 0.6.3 released on 17 January 2019 + +Important changes to the library: + + + Prohibited `PhysicsRigidBody.setKinematic()` on static bodies. + + Used `EmptyShape` to permit linking a bone without + vertices in a `DynamicAnimControl`. + + Added `getSpatial()` methods to `GhostControl` and `RigidBodyControl`. + + Updated the native libraries to version 1.0.24 of `jme3-bullet-native`. + +Important changes to the examples: + + + Added new apps: `HelloDac`, `HelloBoneLink`, `TestHullContact`. + + Added apps from the jme3-examples (sub)project: `TestSimplePhysics`, + `TestRagdollCharacter`, and `TestBoneRagdoll`. + + Added example tuning for CesiumMan model. (model not provided) + + Simplified the example tuning for the Jaime model. + +## Version 0.6.2 released on 6 January 2019 + + + Fixed bug where `SimpleSolidControl.onAdd()` threw a `NullPointerException` + if the control wasn't added to a `PhysicsSpace`. + + Added a `countJoints()` method to `PhysicsRigidBody`. + + Added a `setLimit()` method to `HingeJoint`. + + Allowed vertical translation of heightfields. + + Based on version 2.18 of the `jme3-utilities-heart` library + and JME 3.2.2-stable. + +## Version 0.6.1 released on 28 December 2018 + + + Added an option to calculate local coordinates + in `DynamicAnimControl.findManagerForVertex()`. + + Added a `chainLength` argument to `DynamicAnimControl.setDynamicChain()`. + + Finalized 4 library methods. + + Created a `CameraOrbitAppState` class for use in examples. + + Based on version 2.17 of the `jme3-utilities-heart` library + and JME 3.2.2-beta1. + + Disable scene-graph culling for animated models in examples. + +## Version 0.6.0 released on 15 December 2018 + +Noteworthy additions: + + + An `IKController` class for inverse kinematics. Each `PhysicsLink` maintains + a list of IK controllers. + + 3 IK joint creation methods in `DynamicAnimControl`: + `moveToBody()`, `moveToWorld()`, and `pinToWorld()`. + + Each `DynamicAnimControl` keeps a list of IK joints and disables those + joints when entering ragdoll mode. + + A `BalanceDemo` with 2 examples of `IKController`. + + An `EmptyShape` class. + + A `setDynamicChain()` method in `DynamicAnimControl`. + + Optional `Biped` and `Binocular` interfaces for `DynamicAnimControl` + subclasses. + + A `footprint()` method to calculate the "footprint" of a `PhysicsLink`. + + An `animateSubtree()` method for `DynamicAnimControl`. + + A constructor for a single-ended `Point2PointJoint` with its + constraint already satisfied. + + An `isActive()` method for all collision objects (not just rigid bodies). + + An `isDetached()` method for all physics links (not just attachments). + + A `setContactResponse()` method for physics characters (not just bodies). + + Simple `compareTo()`, `equals()`, and `hash()` methods for collision objects. + +Bugs fixed: + + + Single-ended point-to-point joints were created with incorrect world + locations for their pivots. + + `PhysicsRigidBody` and `PhysicsCharacter` were not cloned properly. + +Debugging improvements: + + + Generally made dumps more compact by trimming trailing zeros. + + In dumps, indicate joints with out-of-space bodies. + + In dumps, indicate joints that lack a dynamic body. + + In dumps, indicate non-responsive rigid bodies. + + In visualizations, draw non-contact physics characters in yellow wireframe. + +Other important changes: + + + Added the concept of a `DynamicAnimControl` being "ready" for dynamic-mode + only after the 1st physics timestep. This helps avert initialization bugs. + + Turned off hardware skinning in `DacLinks.createSpatialData()` to provide + access to the true positions of mesh vertices. + + Modified the `DynamicAnimControl.centerOfMass()` method to also estimate the + velocity vector of the center of mass. + + Links in kinematic mode now update their body's location and velocity for + every frame, instead of just for each timestep. + + Eliminated the `PhysicsJoint.getPivotInWorld()` method. (API change) + + Renamed `DacPhysicsLinks` to `DacLinks`. (API change) + + `PhysicsSpace` accessors now return physics-object collections sorted by ID. + +Other details: + + + Updated the native libraries to version 1.0.21 of `jme3-bullet-native`. + + Based on version 2.16 of the `jme3-utilities-heart` library. + +## Version 0.5.1 released on 5 December 2018 + + + Added a "contact response" option for rigid bodies. + + Added an `isStatic()` method to `PhysicsCollisionObject`. + + Prohibited scaling of `SimplexCollisionShape`. + + Added a `MultiSphere` constructor for a capsule shape with indexed axis. + +Noteworthy changes to debug visualization: + + + Fixed a bug where physics objects and joints continued to be visualized after + setting a filter to exclude them. + + Update debug spatials on every change to `debugMeshNormals` + or `debugMeshResolution`. + + For a `CompoundCollisionShape`, generate a new debug spatial on every frame. + + Visualize non-responsive rigid bodies in yellow. + +Noteworthy changes to `DynamicAnimControl`: + + + Added `centerOfMass()` and `setAttachmentConfig()` methods. + + Collect mesh-vertex coordinates in a `HashSet` (instead of an `ArrayList`) + to increase the efficiency of `createSpatialData()`. + +Other details: + + + Updated the native libraries to version 1.0.20 of `jme3-bullet-native`. + + Based on version 2.15 of the `jme3-utilities-heart` library. + +## Version 0.5.0 released on 29 November 2018 + +Minie moved from the Jme3-utilities Project to a new GitHub repo. + +Noteworthy features added: + + + Added single-ended versions of all 6 `PhysicsJoint` types. + + Added optional heuristics for configuring the center, shape, and + mass of a `PhysicsLink`. + + Added 4 methods for compatibility with the `jme3-bullet` library. + + Cache and re-use debug meshes. + + Added an enable flag and a breaking impulse threshold to every + `PhysicsJoint` object. + + Added an MhGame model for use by `TestDac`. + + Added `SeJointDemo`, `TestLargeMesh` and `TestRectangularSolid` example apps. + + Added a `HullCollisionShape` constructor based on `RectangularSolid`. + + Added `MultiSphere` constructors based on `BoundingSphere` + and `RectangularSolid`. + +Other important changes: + + Debug-mesh properties are now per-collision object, instead of per-shape. + + In the example model tunings, configure masses based on density. + + In `BulletJointDebugControl`, visualize the A and B ends of each joint + in distinct colors. + + Moved 10 assertion-based tests to the library's "test" source set. + + Fixed a bug in the `TestDac` application where controls were not removed + for certain models. + + Fixed a bug in `BulletVehicleDebugControl` where odd-numbered wheels were + never updated. + +Other details: + + + Updated the native libraries to version 1.0.18 of `jme3-bullet-native`. + + Based on version 2.14 of the `jme3-utilities-heart` library. + +## Version 0.4.5 released on 20 November 2018 + +Main features added: + + + New `MultiSphere` collision shape and `MultiSphereDemo` app. + + Configure normals and resolution of the debug mesh for each collision shape. + + Register an init listener for a `BulletDebugAppState`. + + The Puppet model with its licensing history (for the `TestDac` app). + + Test whether a collision shape is convex. + + Vertex counts and volume calculations for various collision shapes. + + Calculate half extents for simplex and hull collision shapes. + + Copy the vertices of a `SimplexCollisionShape`. + +Bugs fixed: + + + Incorrect default limits for `SixDofJoint`. + + Crash in `PhysicsSpace.stepSimulation()` after reading a hull shape + from a model asset. + + Debug mesh is not updated after the shape's margin changes. + + `MyShape.volume()` ignores scaling of capsule shapes. + +Incompatible changes to the library API: + + + Renamed `PhysicsRigidBody.getJoints()` to `listJoints()` and changed its + semantics to reduce aliasing. + + Privatized the joint list in `PhysicsRigidBody`. + + Removed 3 inverse-kinematics stub methods from `DynamicAnimControl`. + + Standardized the `getLowerLimit()` and `getUpperLimit()` methods + in the `TranslationalLimitMotor` class. + + Removed the `getTriangleIndexVertexArray()` method + from the `NativeMeshUtil` class. + +Other details: + + + Updated the native libraries to version 1.0.15 of `jme3-bullet-native`. + + Based on version 2.13 of the `jme3-utilities-heart` library. + +## Version 0.4.4 released on 12 November 2018 + + + Fixed map cloning bugs in `DynamicAnimControl` and + `ConfigDynamicAnimControl`. + + Added a `countJoints()` method and removed the `destroy()` method of + the `PhysicsSpace` class. + + Reduce aliasing in the `BulletDebugAppState` constructor. + +## Version 0.4.3 released on 8 November 2018 + + + Changed `DynamicAnimControl.setMass()` to take a bone name or a physics link. + + Made `MyControlP` aware that `DynamicAnimControl` does not support + local physics. + + Added `DynamicAnimControl` tunings for the MhGame and Puppet models. + +## Version 0.4.2 released on 3 November 2018 + +More changes to `DynamicAnimControl`: + + + Added the capability to release attachments. + + Gave each `PhysicsLink` a name that's distinct from its bone's name. + + Added `hasAttachmentLink()` and `unlinkAttachment()` methods. + + Removed the `isLinkName()` method. + + Renamed the `isBoneLinkName()` method to `hasBoneLink()`. + + Renamed the `unlink()` method to `unlinkBone()`. + + Changed the `attachmentBoneNames()` and `linkedBoneNames()` methods to return + arrays instead of collections. + + Added example tunings for the Ninja and Oto models. + + Detect and reject models with ignoreTransform geometries. + +Other noteworthy changes to Minie: + + + In `RangeOfMotion`, set the joint's angular limits in addition + to its motor limits. + + In `SixDofJoint`, store rotational motors in an array, not a linked list. + +## Version 0.4.1 released on 1 November 2018 + +More design and implementation changes to `DynamicAnimControl`: + + + Added support for attachments nodes. + + The center of a linked rigid body can be offset from its joint. + + Major refactoring to base `AttachmentLink`, `BoneLink`, and `TorsoLink` + on a new `PhysicsLink` class. + + Renamed `JointPreset` class to `RangeOfMotion`. + + Refer to links by reference instead of by name. + + Added a `forceKinematic` option to the `freeze()` methods. + + Renamed many methods. + + Preserve animation data during a `rebuild()`. + + Moved `RagdollCollisionListener` to the `com.jme3.bullet.animation` package. + + Completed the `read()` and `write()` methods. + + Lowered the default for `torsoMass` from 15 to 1. + + Fixed bug where `DynamicAnimControl` reported collisions from other DACs. + + Catch any attempt to set local physics. + + Don't re-order controls unless it's necessary. + +Other noteworthy changes to Minie: + + + Improved dumps and descriptions of joints, physics controls, + collision objects, and rigid bodies. + + Standardized `getPivot()` methods to avoid aliasing. + + Avoided aliasing in `setViewPorts()` methods. + + Added a `getTargetVelocity()` method to `TranslationalLimitMotor`. + + Added an `activate()` method to `PhysicsCollisionObject`. + + Fixed a bug that caused an assertion failure while reading + a `CompoundCollisionShape`. + + Updated the native libraries to v1.0.12 of `jme3-bullet-native`. + +## Version 0.4.0 released on 20 October 2018 + +Extensive design and implementation changes to `KinematicRagdollControl` +and its ilk, now in its own `com.jme3.bullet.animation` package. +`DynamicAnimControl` is now the core, with `ConfigDynamicAnimControl` for +configuration. + + + Bone shapes are now aligned with bone coordinate axes instead of mesh + coordinate axes. + + The inverse-kinematics code has been removed in anticipation of a + complete redesign. + + Kinematic mode now has 4 submodes. + + Rigid body updates now take place just before each physics tick instead of + during scene-graph updates. + + Added an interim tool for tuning a `DynamicAnimationControl`. + + Added example tunings for the Jaime and Elephant models. + + Added per-axis freezing of dynamic joints. + +Other noteworthy changes to Minie: + + + Added a getSpatial() method to the `AbstractPhysicsControl` class and + privatized its `spatial` field. Also added an empty controlRender() method. + + Bugfix: `NullPointerException` in `PhysicsSpace.getGravity()`. + + Bugfix: JVM crashed while reading a `SixDofJoint` from a J3O asset. + + Added check for invalid location in `RigidBodyMotionState.getWorldLocation()` + + Disabled the `isInWorld` checks in `PhysicsRigidBody`. + + Renamed and standardized the accessors of `RigidBodyMotionState`. + + In `RotationalLimitMotor`: renamed the limit/bounce accessors, added + accessors for CFM parameters, added `getAccumulatedImpulse()` and + `getAngle()` methods. + + In `TranslationalLimitMotor`: added accessors for 4 parameters, added + `getOffset()` and `setTargetVelocity()` methods. + + Added `getPhysicsTransform()` and `setPhysicsTransform()` methods to + the `PhysicsRigidBody` class. + + Bypassed `setSpatial()` in `GhostControl` and `RigidBodyControl` in case the + `Spatial` does not change. + + Eliminated unnecessary aliasing in joint constructors. + + Named the debug textures in `BulletDebugAppState`. + + Visualize kinematic bodies in blue instead of magenta, to distinguish + them from dynamic bodies. + +## Version 0.3.5 released on 10 October 2018 + +Enhancements to `KinematicRagdollControl`: + + + Began treating the torso more like a bone. + + Implemented a new algorithm to construct hulls without weight thresholds. + + Redesigned how mass is configured and totaled. + + Lowered the default dispatch threshold from 10 to 0. + + Moved all the code in `RagdollUtils` to other classes. + + Include the torso in `setDamping()`. + + Added `boneMass()`, `getBoneLink()`, `getJointPreset()`, `gravity()`, + `linkedBoneNames()`, `setGravity()`, and `torsoMass()` methods. + +Other noteworthy changes: + + + Added `getPhysicsScale()` and `setPhysicsScale()` methods to + `PhysicsRigidBody`. + + Removed the `space` argument from the addPhysics() and removePhysics() + methods of `AbstractPhysicsControl` and its subclasses. + + Added a list-based constructor for `HullCollisionShape`. + + Fixed a logic bug in `MyObject` where vehicles were not recognized. + + Added a `setPivot` method to `SixDofJoint.setPivot()`. + + Added a `physicsTransform()` method to `RigidBodyMotionState`. + + Added `JointEnd` and `TestRagdollScaling` classes. + + Updated shared libraries to v1.0.7 of `jme3-bullet-native`. + + Removed the unused `PhysicsSpace.initNativePhysics()` method. + +## Version 0.3.4 released on 5 October 2018 + +Enhancements to `KinematicRagdollControl`: + + + Removed the `weightThreshold = -1` hack. + + Replaced `boneList` and `RagdollPreset` with a joint map. + + Changed coordinate translation to utilize animated geometries + instead of the controlled spatial. + + Eliminated the temporary removal of the controlled spatial from the scene. + + Changed to continue updating ragdoll even after the controlled spatial moves. + +Other noteworthy changes: + + + Fixed 2 logic errors in `CylinderCollisionShape.canScale()`. + + Added result validation to `PhysicsRigidBody.getPhysicsLocation()`. + + Fixed JME issue #931. + + Updated shared libraries to v1.0.5 of `jme3-bullet-native`. + + Improved `applyScale` option in `GhostControl` and `RigidBodyControl` so that + it will fall back to uniform scaling (if necessary) or skip rescale + (if scale is unchanged). + + Added an `isEmpty()` method to the `PhysicsSpace` class. + + Added `TestSetScale`, `TestIssue918`, and `TestIssue919`. + +## Version 0.3.3 released on 2 October 2018 + + + Added `applyScale` option to `RigidBodyControl` and `GhostControl`. + + Added default margin for collision shapes other than capsule and sphere. + + Eliminated runtime dependency on JME's `jme3-bullet-native` library. + + Removed `TestIssue896`. + +## Version 0.3.2 released on 28 September 2018 + + + Made many classes `JmeCloneable`, especially physics controls. + + Added custom debug materials to collision objects. + + Added `canScale()` method to collision shapes. + + Worked around JME issue #919. + + Prevented setting the margin of a capsule/sphere shape. + + Implemented limb damping in `KinematicRagdollControl`. + + Added `getTorso()` method to `KinematicRagdollControl`. + + Added check for rotation/translation of a heightfield rigid body. + + Converted `PhysicsBoneLink` to a standalone class. + + Removed unnecessary constructor from CollisionShape. + + Added tests. + +## Version 0.3.1 released on 24 September 2018 + + + Fixed JME issue #896 and added a test for it. + + Disabled `getMargin()` and `setMargin()` for capsule and sphere shapes. + + Initialized the scale and margin of compound shapes. + + Removed various methods and arguments. + + Added `TestSetMargin` to the test project. + +## Version 0.3.0 released on 23 September 2018 + + + Fixed JME issue #740. + + Standardized the design of constructors and accessors to reduce aliasing + of vectors and quaternions and enable the use of caller-allocated storage. + + Implemented a more practical approach to filtering debug objects. + + Simplified `PhysicsCollisionEvent` by eliminating event types. + + Renamed 2 `PhysicsJoint` methods that misspelled "bodies". + + Removed many needless fields, methods, and constructors. + + Made the `VehicleTuning` class `JmeCloneable` and `Savable`. + + Addressed the possibility of multiple physics controls added to the + same Spatial. + + Replaced 6 parameters of `VehicleWheel` with a `VehicleTuning` reference. + + Eviscerated 5 `cloneForSpatial()` methods. + + Based on version 2.10 of the jme3-utilities-heart library. + +## Version 0.2.10 released on 12 September 2018 + + + Fixed JME issue #898. + + Require collision margin > 0 . + + Changed default collision margin from 0 to 0.04 . + + Disabled setMargin() for SphereCollisionShape. + + Don't allow dynamic bodies to have heightfield or plane shapes. + + Publicized loggers. + + Added massForStatic constant in PhysicsRigidBody. + + Added 2 tests. + + Privatized the HeightfieldCollisionShape.createShape() method. + +## Version 0.2.9 released on 9 September 2018 + + + Removed PhysicsCollisionEventFactory. + + Removed HeightfieldCollisionShape.createJmeMesh(), + VehicleWheel.getGroundObject(), and a constructor for PhysicsGhostObject. + + Privatized various methods. + + Fixed JME issue #894. + + Implemented a cleaner fix for JME issue #889. + + Deal with scale changes in physics-debug controls. + + Decided that physics-debug controls should implement neither JmeCloneable + nor Savable. + + Added validation of method arguments. + + Finalized various fields. + + Created the jme3utilities.minie.test package. + +## Version 0.2.8 released on 3 September 2018 + + + Removed some unnecessary methods. + + Reduced the scope of many methods. + + Renamed getMass() to mass() in MyControlP. + + Fixed JME issue #889. + + Added validation of method arguments, plus some assertions. + + Based on version 2.8 of the jme3-utilities-heart library. + +## Version 0.2.7 released on 1 September 2018 + + + Don't setLocalScale() on spatials controlled by debug controls; this is + related to JME issue #887. + + Handle ignoreTransforms in GhostControl and RigidBodyControl. + + Describe rigid bodies and RigidBodyControls similarly. + + Describe shape scaling and spatial scaling similarly. + + Describe the half extents of box shapes. + +## Version 0.2.6 released on 31 August 2018 + + + Fixed JME issues 883 and 887. + + Ensured that debugViewPorts[] gets initialized in BulletAppState. + + Changed AbstractPhysicsControl to handle ignoreTransform. + + Changed DebugAppStateFilter interface to consider only Savable objects. + + Added validation of method arguments, plus some assertions. + + Reduced the scope of many fields and methods. + + Finalized some fields. + + Removed some unused fields and methods. + + Added jme3-bullet-native runtime dependency to POM. + + Replaced iterators with enhanced loops (for readability). + + Standardized logging. + +## Version 0.2.5 released on 24 August 2018 + + + Bugfix: PhysicsDumper prints incorrect number of vehicles. + + Bugfix for JME issue #867 contributed by Riccardo Balbo. + + Privatized numerous protected fields. + + Removed 3 PhysicsSpace constructors. + + Enhanced PhysicsDumper to handle app states and print the joint list and + (non-identity) orientation for each rigid body. + + Added BulletAppState.getBroadphaseType(). + + Added validation of method arguments. + + Changed BulletAppState.setWorldMax() and .setWorldMin() to avoid aliasing. + +## Version 0.2.4 released on 22 August 2018 + + + Renamed MinieVersion.getVersionShort() to versionShort(). + + Used MyAsset to create debug materials. + + In BulletDebugAppState, only render viewports that are enabled. + + Based on version 2.7 of the jme3-utilities-heart library. + +## Version 0.2.3 released on 17 August 2018 + ++ Renamed ray-test flag accessors in PhysicsSpace class. (API change) ++ Added maxSubSteps() method to the PhysicsSpace class. ++ Include more detail when dumping a physics space. ++ Based on version 2.6 of the jme3-utilities-heart library. + +## Version 0.2.2 released on 24 July 2018 + ++ Enhanced PhysicsDescriber to describe axes of cone shapes. ++ Based on version 2.5 of the jme3-utilities-heart library. ++ Remove an obsolete TODO comment. + +## Version 0.2.1 released on 19 February 2018 + ++ Changed BulletDebugAppState to accept an array of viewports to add scenes to, + instead of creating its own viewport. ++ Added getAxis() method to the ConeCollisionShape class. ++ Allow uniform scaling of capsule, cylinder, and sphere shapes. + +## Version 0.2.0 released on 2 February 2018 + ++ Added axisIndex(), describe(), describeType(), halfExtents(), height(), + radius(), setHalfExtents(), setHeight(), and setRadius() methods to the + MyShape utility class. ++ Copied source files from jme3-bullet library and corrected many minor issues. + +## Version 0.1.2 released on 26 January 2018 + +This was the initial baseline release, based largely on code formerly +included in the jme3-utilities-heart, jme3-utilities-debug, and +jme3-utilities-x libraries. diff --git a/MinieLibrary/release-notes.md b/MinieLibrary/release-notes.md index 8a24e6e0b..b78e0c30f 100644 --- a/MinieLibrary/release-notes.md +++ b/MinieLibrary/release-notes.md @@ -1,1245 +1,1245 @@ -# Release log for the Minie library, DacWizard, MinieExamples, and VhacdTuner - -## Version 7.7.0 released on 31 August 2023 - -+ Fixed bugs: - + `NullPointerException` after rebuilding a `DynamicAnimControl` - + unhelpful warnings while cloning a `PhysicsRigidBody` - + `SplitDemo` app crashes caused by attempting to rotate heightfields - -+ Added to the library: - + the `MinkowskiSum` collision shape - + the `ClassFilter` class, for filtering physics objects -+ Specified a more precise return type for `Convex2dShape.getBaseShape()`. -+ `DynamicAnimControl` now logs a warning if armature scaling exceeds 1%. - -+ Improvements to the `DacWizard` application: - + Re-scale the 3-D model for each pose. - + Added a "rebuild" action to test mode, bound to `KEY_F3`. -+ Improvements to the `MinieDump` application: - + Added command-line options to specify an asset root. - + Added help/usage hints. -+ Improvements to the `MinieExamples` applications: - + Added more shapes to `DropTest` and `SplitDemo`. - + Reduced the risk of fallthru in `DropTest`, by disabling contact filtering. - -+ Based on v3.6.1-stable of JMonkeyEngine and v8.7.0 of the Heart library. -+ Built using Gradle v8.3 . -+ Updated the native libraries to v18.5.2 of Libbulletjme. - -## Version 7.6.0 released on 3 June 2023 - -+ Bugfix: the gravity of a rigid body isn't read/written/cloned -+ Bugfix: ignore lists are never cloned for non-joined collision objects -+ Bugfix: `IllegalStateException` in `DropTest` while creating a ragdoll -+ Allow zero-mass kinematic rigid bodies. -+ Added the an "addLinksForTris" parameter to `SoftBodyControl`. - -+ Based on v8.6.0 of the Heart library. -+ Built using Gradle v8.1.1 . -+ Updated the native libraries to v18.3.0 of Libbulletjme. - -## Version 7.5.0 released on 13 April 2023 - -+ Bugfix: during cloning, Bullet's ignore list - gets out-of-synch with the JVM list -+ Added the `applyAllExceptIgnoreListTo()` method - to the `RigidBodySnapshot` class. -+ Made private fields `static` in the tutorial apps. -+ Built using Gradle v8.1.0 . - -## Version 7.4.0 released on 24 March 2023 - -+ Fixed bugs: - + `BetterCharacterControl` oscillates while unducking near an overhang - + `BetterCharacterController.isOnGround()` is unreliable - + `TestIssue18Heightfield` application throws an `IllegalArgumentException` - + excessive logging from the `TestIssue13` application -+ Implemented kinematic modes for `BetterCharacterControl` - and `JoinedBodyControl`. -+ Deprecated 7 library methods for obtaining native IDs: - + `CollisionShape.getObjectId()` - + `CollisionSpace.getSpaceId()` - + `PhysicsCollisionObject.getObjectId()` - + `PhysicsJoint.getObjectId()` - + `RigidBodyMotionState.getObjectId()` - + `RotationalLimitMotor.getMotor()` - + `TranslationalLimitMotor.getMotor()` -+ Based on: - + v3.6.0-stable of JMonkeyEngine, - + v3.0.0 of the jME-TTF library, - + v8.3.2 of the Heart library, - + v1.0.0 of the Acorus library, - + v0.7.5 of the Wes library, and - + v0.9.35 of the jme3-utilities-nifty library. - -## Version 7.3.0+for36 released on 3 March 2023 - -+ Fixed bugs: - + ignored collision objects may be garbage-collected prematurely - + adding a `DynamicAnimControl` to a spatial resets its armature - + `PhysicsCollisionObject.cloneIgnoreList()` throws a `NullPointerException` - + `BetterCharacterControl.setPhysicsLocation()` never updates the field - + tasks passed to `PhysicsSpace.enqueueOnThisThread()` never execute - + `TestAttachDriver` example doesn't reset properly (JME issue 1975) -+ Added the `JointedBodyControl` class. -+ Added 7 methods to the library: - + `CollisionSpace.isForceUpdateAllAabbs()` - + `CollisionSpace.setForceUpdateAllAabbs()` - + `DynamicAnimControl.blendToKinematicMode(KinematicSubmode, float, Transform)` - + `DynamicAnimControl.saveCurrentPose()` - + `DynamicAnimControl.setKinematicMode(KinematicSubmode)` - + `PhysicsCollisionObject.listIgnoredPcos()` - + `PhysicsCollisionObject.setIgnoreList(PhysicsCollisionObject[])` -+ Added the "reset" kinematic submode to `DynamicAnimControl`. -+ Publicized 3 library methods: - + a constructor for the `IndexedMesh` class - + `DacLinks.getTransformer()` - + `RagUtils.findMainJoint()` -+ Deprecated 2 library methods: - + `PhysicsCollisionObject.listIgnoredIds()` - + `PhysicsCollisionObject.setIgnoreList(long[])` -+ Added some runtime checks and strengthened others. -+ Made improvements to the DacWizard application: - + added the "torso" screen to select the torso's main bone - + added posing capability to the "load" and "test" screens - + fixed a bug that caused generation of syntactically incorrect Java code - + fixed a bug where the wrong main bone was used to estimate ranges of motion - + when generating Java code, provide a `configure()` method - + generate practical classnames for Java code -+ Based on: - + v3.6.0-beta3 of JMonkeyEngine, - + v1.6.0 of the SimMath library, - + v8.3.1+for36 of the Heart library, - + v0.9.18+for36 of the Acorus library, - + v0.7.3+for36 of the Wes library, and - + v0.9.34+for36 of the jme3-utilities-nifty library. -+ Built using Gradle v8.0.2 . -+ Updated the native libraries to v18.1.0 of Libbulletjme. - -## Version 7.2.0 released on 24 January 2023 - -+ Bugfix: `PhysicsCollisionObject.findInstance()` - creates a weak global reference that can never be deleted -+ Bugfix: `DynamicAnimControl.totalMass()` returns `NaN` even when - the control is added to a `Spatial` -+ Bugfix: weak global references in `PhysicsCollisionObject` and `MultiBody` - can never be deleted -+ Added 2 methods to the library: - + `CollisionSpace.jniEnvId()` - + `NativeLibrary.jniEnvId()` -+ Updated the native libraries to v17.5.4 of Libbulletjme. - -## Version 7.1.0 released on 16 January 2023 - -+ Added the capability to generate cylinder shapes in a ragdoll. -+ Publicized the `RagUtils.makeRectangularSolid()` method. -+ Added `DynamicAnimControl` tunings for a Mixamo rig. -+ Added the capability to display angles in degrees or radians in DacWizard. -+ Updated `DacWizard` and `VhacdTuner` to use v0.9.33 - of the jme3-utilities-nifty library. - -## Version 7.0.2 released on 2 January 2023 - -+ Bugfix: `NullPointerException` in `rebuildRigidBody()` while de-serializing - an old model -+ Bugfix: DacWizard doesn't write rotation orders to Java source code - -## Version 7.0.1 released on 1 January 2023 - -Bugfix: `NullPointerException` in `PhysicsCollisionObject.readPcoProperties()` - -## Version 7.0.0 released on 24 December 2022 - -+ API changes: - + Privatized `PhysicsCollisionObject.getCollisionFlags()` (a native method) - + Added the `static` qualifier to `PersistentManifolds.listPointIds()` - + Renamed the public logger in the `ConvexShape` class to avoid conflict. - + Added a 2nd argument to `PhysicsBody.cloneJoints()` - + Corrected the return type of `CharacterController.jmeClone()` - + Finalized 3 classes: - + `NativeSoftBodyUtil` - + `PhysicsRayTestResult` - + `PhysicsSweepTestResult` - -+ Bug fixes: - + `DynamicAnimControl` may pass illegal arguments to `MyMath.slerp()` - + assertion failure when `toString()` is invoked on a collision object - or physics joint with no native object assigned - + out-of-range exception upon re-entering DacWizard's "bones" screen - with a different model - + transforms are not updated for the `getCalculatedOriginA()` and - `getCalculatedOriginB()` methods in the `New6Dof` class - + `getPhysicsLocationDp()` and `getPhysicsRotationDp()` return incorrect - values for a soft body - + cloning bugs: - + physics joints are cloned inaccurately - + ignore lists are cloned inaccurately - + cloning or rebuilding a collision object results in - different collision flags - + the feedback parameter of a `Constraint` isn't cloned - + `DacLinks` incompletely cloned - + `BoneLink.tmpMatrix` is shared between clones - + serialization bugs: - + `NullPointerException` in `PreComposer.read()` - + `RigidBodyMotionState` is never serialized - + the pivot offsets of single-ended constraints - are de-serialized incorrectly - + `PhysicsLink.density` is never saved or loaded - + the feedback parameter of a `Constraint` is never saved or loaded - + the `bindTransform` and `preComposer` fields of a `DacLinks` - are never saved or loaded - -+ Publicized the `PhysicsLink.velocity()` method. -+ Added 9 double-precision setters: - + `CharacterController.warpDp()` - + `MultiBodyCollider.setPhysicsLocationDp()` - + `MultiBodyCollider.setPhysicsRotationDp(Matrix3d)` - + `PhysicsCharacter.setPhysicsLocationDp()` - + `PhysicsGhostObject.setPhysicsLocationDp()` - + `PhysicsGhostObject.setPhysicsRotationDp()` (2 signatures) - + `PhysicsRigidBody.setPhysicsRotationDp(Matrix3d)` - + `PhysicsSoftBody.setPhysicsLocationDp()` -+ Added 8 other double-precision methods: - + `CollisionShape.getScaleDp()` - + `CollisionSpace.rayTestDp()` - + `ManifoldPoints.getPositionWorldOnADp()` - + `ManifoldPoints.getPositionWorldOnBDp()` - + `PhysicsCollisionObject.getPhysicsRotationMatrixDp()` - + `RigidBodyMotionState.getLocationDp()` - + `RigidBodyMotionState.getOrientationMatrixDp()` - + `RigidBodyMotionState.getOrientationQuaternionDp()` -+ Added 6 other methods: - + `New6Dof.calculatedBasisA()` - + `New6Dof.calculatedBasisB()` - + `New6Dof.getRotationMatrix()` - + `PhysicsCollisionObject.collisionFlags()` - + `PhysicsDescriber.describeMatrix()` - + `PhysicsDumper.dump(PhysicsJoint, String)` - -+ Added `INFO`-level log messages - to the `New6Dof` and `PhysicsRigidBody` classes. -+ Made incremental improvements to the `PhysicsDumper` output format. -+ Strengthened argument validation. -+ Updated the native libraries to v17.4.0 of Libbulletjme. -+ Updated `DacWizard` and `VhacdTuner` to use v0.9.32 - of the jme3-utilities-nifty library. -+ Built using Gradle v7.6 . - -## Version 6.2.0 released on 13 November 2022 - -+ Added 3 methods: - + `PhysicsSpace.isCcdWithStaticOnly()` - + `PhysicsSpace.setCcdWithStaticOnly()` - + `NativeLibrary.countClampedCcdMotions()` -+ Updated the native libraries to v17.0.0 of Libbulletjme. -+ Based on v0.9.17 of the Acorus library. - -## Version 6.1.0 released on 1 October 2022 - -+ Bugfix: `PreComposer` isn't properly cloned or serialized -+ Added library support for dynamic collision-shape splitting: - + Added 8 public constructors: - + `ChildCollisionShape(Vector3f, CollisionShape)` - + `CompoundMesh(CompoundMesh)` - + `CompoundMesh(CompoundMesh, Vector3f)` - + `GImpactCollisionShape(CompoundMesh, Vector3f)` - + `HullCollisionShape(Vector3f...)` - + `IndexedMesh(FloatBuffer)` - + `MeshCollisionShape(boolean, CompoundMesh)` - + `MultiSphere(Vector3f[], float...)` - + Added 17 other public methods: - + `ChildCollisionShape.split()` - + `CollisionShape.aabbCenter()` - + `CollisionShape.canSplit()` - + `CollisionShape.scaledVolume()` - + `CollisionShape.toSplittableShape()` - + `CompoundCollisionShape.connectivityMatrix()` - + `CompoundCollisionShape.countGroups()` - + `CompoundCollisionShape.split()` - + `CompoundMesh.maxMin()` - + `CompoundMesh.split()` - + `ConvexShape.toHullShape()` - + `GImpactCollisionShape.split()` - + `HullCollisionShape.split()` - + `IndexedMesh.copyTriangle()` - + `IndexedMesh.maxMin()` - + `IndexedMesh.split()` - + `MeshCollisionShape.split()` -+ Other library enhancements: - + Added the `createGImpactShape()` method - to the `CollisionShapeFactory` class. - + Added the `pairTest()` method to the `CollisionSpace` class. - + Added the `countMeshTriangles()` to the `GImpactCollisionShape` class. -+ Added the `SplitDemo` and `SweepDemo` applications. -+ Enhanced the `ShapeGenerator` class to use diverse axes when generating - capsule, cone, and cylinder shapes. -+ Added the "teapotGi" collision shape to the MinieAssets project. -+ Based on v8.2.0 of the Heart library. -+ Updated the native libraries to v16.3.0 of Libbulletjme. - -## Version 6.0.1 released on 29 August 2022 - -+ Bugfix: `DacLinks` never re-enables hardware skinning (performance issue) -+ VhacdTuner GUI tweaks: - + Removed the "ACDMode" buttons. - + Added buttons to toggle the "async" setting. - + Added a button to quit ranking the newest test result. -+ Added glTF loading capability to DacWizard. - -## Version 6.0.0 released on 25 August 2022 - -+ Protected the no-arg constructors of 6 abstract classes. (API changes) -+ Bugfix: issue #30 (`NullPointerException` after removing - `DynamicAnimControl` from a `Spatial`) -+ Bugfix: I/O resources not safely closed in `VHACDParameters`. -+ Added V-HACD version 4 including the `Vhacd4`, `Vhacd4Parameters, `FillMode`, - and `Vhacd4Hull` classes plus a new `HullCollisionShape` constructor and a - new `CollisionShapeFactory` method. -+ Added the VhacdTuner sub-project. -+ Added the `toMap()` method to the `VHACDParameters` class. -+ Updated the native libraries to v16.1.0 of Libbulletjme. - -## Version 5.1.0 released on 6 August 2022 - -+ Bugfix: `PhysicsRigidBody.setInverseInertiaLocal()` and - `PhysicsRigidBody.updateMassProps()` don't update the world inertia tensor -+ Bugfix: when rebuilding a rigid body, many properties are lost -+ Added a `DynamicAnimControl` mechanism to report the completion - of a blend-to-kinematic operation. -+ Added the `CompletionListener` interface. -+ Added the `RigidBodySnapshot` class. -+ Added the `setIgnoreList()` method to the `PhysicsCollisionObject` class. -+ Added a simpler constructor to `PhysicsSoftSpace`. -+ Publicized the `rebuildRigidBody()` method. -+ Updated the native libraries to v16.0.0 of Libbulletjme. -+ Built using Gradle v7.5.1 . - -## Version 5.0.1 released on 2 August 2022 - -+ Bugfix: static rigid body misbehaves after being assigned a positive mass -+ Bugfix: `DynamicAnimControl` with `AnimComposer` exhibits glitches during - blends to kinematic mode -+ Bugfix: when rebuilding a rigid body, its ignore list is lost -+ Based on v8.1.0 of the Heart library. -+ Built using Gradle v7.5 . - -## Version 5.0.0 released on 11 July 2022 - -+ Replaced the "ano" build with "droid" build that includes Java classes. -+ Changes to the library API: - + Replaced the `DebugMeshNormals` enum with `MeshNormals` (from Heart). - + Protected the no-arg constructors of the `DacConfiguration` - and `DacLinks` classes. - + Protected the public constructors of 3 classes: - + `AbstractPhysicsDebugControl` - + `IKController` - + `SoftPhysicsJoint` - + Qualified 17 utility classes as `final`. - + Removed the `static` qualifier from the protected `createWireMaterial()` - method of the `BulletDebugAppState` class. - + Deleted the deprecated `setContractCalcArea3Points()` - method of the `PhysicsCollisionEvent` class. - -+ Library bugfixes: - + `PhysicsCharacter.onGround()` is unreliable (stephengold/Libbulletjme#18) - + `TorsoLink` continues writing the model transform - after a blend to kinematic completes - + `DynamicAnimControl` rebuilds the ragdoll for minute changes to bone scaling - + `TorsoLink` plays some bone animations, even in dynamic mode - + outdated constant values in `ConfigFlag` - + `DynamicAnimControl` is still marked "ready" - after removal from the `PhysicsSpace` - + `DebugMeshCallback.maxDistance()` modifies the vertex list - + `ConfigFlag.describe()` ignores 3 flags - -+ Other library improvements: - + Warn if the native library version differs from the expected version. - + Throw an exception in `AbstractPhysicsControl.jmeClone()` - if the control is added to a `PhysicsSpace`. - + Add capability to specify the main bone of a ragdoll, - which needn't be a root bone. - + Added accessors for global deactivation settings: - + `PhysicsBody.getDeactivationDeadline()` - + `PhysicsBody.isDeactivationEnabled()` - + `PhysicsBody.setDeactivationDeadline()` - + `PhysicsBody.setDeactivationEnabled()` - + Added debug visualizations of rigid-body angular velocities - and soft-body wind velocities. - + Added a "relative tolerance" parameter to `DynamicAnimControl`. - + Reimplemented `BulletDebugAppState` using `BaseAppState`. - + Added the `DeformableSpace` class that supports both multibodies - and soft bodies. - + Added `SDF_MDF` and `SDF_RDN` bitmasks to `ConfigFlag`. - -+ Added the `Pachinko` and `Windlass` apps to MinieExamples. -+ Added the `HelloGhost` and `HelloWind` apps to TutorialApps. -+ Updated the project URL in the POM. -+ Added 10 "package-info.java" files. -+ Based on: - + v8.0.0 of the Heart library, - + v0.7.2 of the Wes library, - + v0.9.16 of the Acorus library, and - + v0.9.30 of the jme3-utilities-nifty library. -+ Updated the native libraries to v15.2.1 of Libbulletjme. -+ Added the "checkstyle" plugin to the build. - -## Version 4.9.0 released on 2 May 2022 - -+ Eliminated the last dependency on JCenter! -+ Bugfix: `IllegalArgumentException` caused by slerps in `TorsoLink` -+ Changed `CollisionShapeFactory` to skip meshes without triangles. -+ Added the `GearJoint` class. -+ Changes to the apps: - + Bugfix: rigid body becomes deactivated in `HelloContactReponse` - + Bugfix: `get(Limits.TextureAnisotropy)` returns `null` on some platforms - + Renamed `TestDebugToPost` and moved it from MinieExamples to TutorialApps. - + Added `TestGearJoint` to MinieExamples. - + Added 3rd body to `HelloDeactivation` for a visual reference point. - + Added monkey-head test to `TestSoftBodyControl`. - + Added display-settings editors to `NewtonsCradle` and `RopeDemo`. - + Enabled window resizing for `DacWizard` and all apps in MinieExamples. -+ Based on: - + v3.5.2-stable of JMonkeyEngine, - + v1.5.0 of the SimMath library, - + v7.6.0 of the Heart library, - + v0.9.15 of the Acorus library, and - + v0.9.29 of the jme3-utilities-nifty library. -+ Built using Gradle v7.4.2 . -+ Updated the native libraries to v14.3.0 of Libbulletjme. - -## Version 4.8.1 released on 29 March 2022 - -+ Bugfix: issue #23 (access violations on 64-bit Windows) -+ Split off "TutorialApps" from the "MinieExamples" sub-project. -+ Based on: - + v0.9.11 of the Acorus library and - + v0.9.28 of the jme3-utilities-nifty library. -+ Updated the native libraries to v14.2.0 of Libbulletjme. - -## Version 4.8.0 released on 20 March 2022 - -+ Added native libraries for the MacOSX_ARM64 platform, - so that Minie applications can run on "Apple Silicon" Macs. -+ Created the "+ano" (Android natives only) build, for use in - Android signed bundles (for distribution via Google Store). -+ Added the `contactErp()`, `jointErp()`, `setContactErp()`, - and `setJointErp()` methods to the `SolverInfo` class, - to give applications better control over joint elasticity. -+ Based on v3.5.1-stable of JMonkeyEngine. -+ Updated the native libraries to v14.1.0 of Libbulletjme. - -## Version 4.7.1 released on 11 March 2022 - -+ Bugfix: `PhysicsCollisionEvent.getNormalWorldOnB()` returns wrong value -+ Bugfix: issue #20 (`btAssert` fails after `HingeJoint.setAngularOnly(true)`) -+ Added the `needsCollision()` method to the `CollisionSpace` class. - This method can be overridden for dynamic collision filtering. -+ Added the `ContactListener` interface for immediate handling of rigid-body - contacts. -+ Added the `ManifoldPoints` utility class to access the properties - of a contact point without instantiating a `PhysicsCollisionEvent`. -+ Added the `PersistentManifolds` utility class to access the properties - of a contact manifold. -+ Added 8 methods to the `PhysicsSpace` class: - + `addContactListener(ContactListener)` - + `countManifolds()` - + `listManifolds()` - + `onContactEnded(PhysicsCollisionObject, PhysicsCollisionObject, long)` - + `onContactProcessed(PhysicsCollisionObject, PhysicsCollisionObject, long)` - + `onContactStarted(PhysicsCollisionObject, PhysicsCollisionObject, long)` - + `removeContactListener(ContactListener)` - + `update(float, int, boolean, boolean, boolean)` - to enable callbacks to specific `ContactListener` methods -+ Deprecated the `PhysicsCollisionEvent.setContactCalcArea3Points()` method - in favor of the corresponding `ManifoldPoints` method. -+ Deleted placeholder files from class jars. -+ Added the `ConveyorDemo` and `JointElasticity` apps to MinieExamples. -+ Based on: - + v7.4.1 of the Heart library, - + v0.7.1 of the Wes library, - + v0.9.10 of the Acorus library, and - + v0.9.27 of the jme3-utilities-nifty library. -+ Built using Gradle v7.4.1 . -+ Updated the native libraries to v14.0.0 of Libbulletjme. - -## Version 4.6.1 released on 24 January 2022 - -+ Restored support for the MacOSX32 platform. -+ Based on: - + v3.5.0-stable of JMonkeyEngine, - + v7.2.0 of the Heart library, - + v0.6.8 of the Wes library, - + v0.9.6 of the jme3-utilities-ui library, and - + v0.9.24 of the jme3-utilities-nifty library. -+ Built using Gradle v7.3.3 . -+ Updated the native libraries to v12.7.1 of Libbulletjme, which - include the fix for bullet3 issue 4117. - -## Version 4.6.0 released on 4 December 2021 - -+ Bugfix: issue #19 (zero-thickness mesh shape crashes the JRE) -+ Dropped support for the MacOSX32 platform. -+ Based on v3.4.1-stable of JMonkeyEngine. -+ Changes to the examples: - + Solved 2 null-pointer exceptions in Jme3Examples. - + Added the `TestInsideTriangle` and `TestIssue19` apps to MinieExamples. - + Split off the `PhysicsDemo` class from `AbstractDemo`. - + Built Jme3Examples using Java 8 . -+ Added the `isInsideTriangle()` method to the `NativeLibrary` class. -+ Guarded some low-level logging code to improve efficiency. -+ Built using Gradle v7.3.1 . -+ Updated the native libraries to v12.6.0 of Libbulletjme, which includes - Bullet updates through 13 November 2021. - -## Version 4.5.0 released on 19 November 2021 - -+ Cached the methods that free native objects, to improve performance. -+ Added the `setPivotInB()` method to the `Anchor` class. -+ Added a mesh customization API to the `DebugShapeFactory` class, - to support (for example) debug materials that need barycentric coordinates. -+ Publicized the `worldMax()` and `worldMin()` methods - in the `BulletAppState` class. -+ Improvements to examples: - + Solved `UnsatisfiedLinkError` crashes in 5 apps. - + Added the `TestIssue18GImpact` app. - + Addressed JME issue 1630 in `TestBetterCharacter`. -+ Updated the native libraries to v12.5.0 of Libbulletjme, - which includes contact filtering for GImpact shapes. -+ Built using Gradle v7.3 . - -## Version 4.5.0-test1 released on 25 October 2021 - -+ Bugfix: invalid contact points for heightfield/mesh shapes (issue #18) -+ Added a flag to disable contact filtering on a per-shape basis. -+ Added accessors to the `CollisionSpace` class for the collision dispatcher's - "deterministic" option. -+ Made all native physics objects `Comparable` (for use in collections). -+ Improvements to examples: - + Added 2 tutorial apps for character physics: - `HelloWalkOtoBcc` and `HelloWalkOtoCc`. - + Added 2 tests for issue #18: - `TestIssue18Heightfield` and `TestIssue18Mesh`. - + Made some tutorial apps more comparable. - + Created a merged mesh shape for the table in `PoolDemo` (more efficient). - + Reduced the camera zoom speed in `PoolDemo` (to avoid overshooting). -+ Updated the native libraries to v12.4.1 of Libbulletjme. - -## Version 4.4.0 released on 1 October 2021 - -+ Added 10 double-precision accessors and created an API dependency - on v1.4.1 of Paul's SimMath library. -+ Added more flexible customization of chassis axes in `VehicleController`. -+ Bugfix: incompatibility with Java v7 -+ Added the `NegativeAppDataFilter` class (for obstructions). -+ Added the `listInternalJoints()` method to the `RagUtils` class. -+ Added the example app `HelloServo`. -+ Improved diagnostic messages in `DacWizard`. -+ Based on v7.1.0 of the Heart library and v0.9.23 of the - jme3-utilities-nifty library. -+ Updated the native libraries to v12.2.2 of Libbulletjme, which includes - Bullet updates through 20 September 2021. - -## Version 4.3.0 released on 22 August 2021 - -+ Bugfix: contact tests report events with positive separation distance -+ Bugfix: `NullPointerException` while previewing an erroneous model during - DacWizard step #2 (the "load" step) -+ Bugfix: pure virtual call in native libraries (issue #17) -+ Added support for Quickprof profiling of native code. -+ Added support for multithreaded physics spaces. -+ Added the `TowerPerformance` application for performance testing. -+ Tweaked the behavior of `PhysicsRigidBody.setKinematic()`. -+ Bypass the asset cache when loading models into DacWizard. -+ Updated the native libraries to v12.0.0 of Libbulletjme - (includes performance enhancements). -+ Based on: - + LWJGL v3 (to improve support for non-US keyboards), - + v7.0.0 of the Heart library, - + v0.6.7 of the Wes library, - + v0.9.5 of the jme3-utilities-ui library, and - + v0.9.22 of the jme3-utilities-nifty library. -+ Built using Gradle v7.2 . - -## Version 4.2.0 released on 24 June 2021 - -+ Bugfix: Libbulletjme issue #7 (GImpact contact tests always fail) -+ Dump the bounds and ignore list of each `PhysicsGhostObject`. -+ Added `hasClosest()` and `hasContact()` methods - to the `CollisionSpace` class. -+ Added a public `getShapeType()` method to the `CollisionShape` class. -+ Document the lack of collision detection between 3 concave shapes. -+ Use a migrated Jaime model in MinieExamples. -+ Updated the native libraries to v10.5.0 of Libbulletjme, which includes - Bullet v2 updates through 12 May 2021. -+ Built using Gradle v7.1 . - -## Version 4.1.1 released on 1 June 2021 - -+ Bugfix: issue #16 (MinieExamples uses deprecated classes) -+ Based on: - + v3.4.0-stable of JMonkeyEngine, - + v2.2.4 of the jme-ttf library, - + v6.4.4 of the Heart library, - + v0.6.6 of the Wes library, - + v0.9.4 of the jme3-utilities-ui library, and - + v0.9.20 of the jme3-utilities-nifty library. -+ Built using Gradle v7.0.2 . - -## Version 4.1.0+for34 released on 25 April 2021 - -+ Bugfix: `RagUtils.ignoreCollisions()` doesn't handle cycles correctly -+ Bugfix: `NullPointerException` while reading a `DacLinks` from an XML file -+ Added `setPivotInA()` and `setPivotInB()` methods to `Point2PointJoint`. -+ Added `HelloDoor` application to the MinieExamples subproject. -+ Updated the native libraries to v10.3.1 of Libbulletjme, which includes - Bullet v2 updates through 21 April 2021. -+ Based on: - + v3.4.0-beta1 of JMonkeyEngine, - + v6.4.3+for34 of the Heart library, - + v0.6.3+for34 of the Wes library, - + v0.9.3+for34 of the jme3-utilities-ui library, and - + v0.9.19+for34 of the jme3-utilities-nifty library. -+ Built using Gradle v7.0 . - -## Version 4.0.2 released on 23 February 2021 - -+ Bugfix: debug visualization not updated for kinematic rigid bodies -+ Updated the native libraries to v10.2.0 of Libbulletjme. - -## Version 4.0.1 released on 16 February 2021 - -+ Bugfix: stack overflow while cloning a `DynamicAnimControl` -+ Upgraded to JUnit v4.13.2 . - -## Version 4.0.0 released on 11 February 2021 - -+ Bug fixes: - + debug visualization of a body jitters relative to its `PhysicsControl`. - + `DacWizard` generates syntactically incorrect source code for some locales - + issue #14 (suspension lengths of a `PhysicsVehicle` are not initialized) - + `IllegalArgumentException` while cloning a `PhysicsVehicle` - in a `PhysicsSpace` -+ A change to the library API: - + Delete the `angularMomentum()` and `kineticEnergy()` methods - from the `MultiBody` class. -+ Published to MavenCentral instead of JCenter. -+ Added a `render()` method to the `AbstractPhysicsControl` class. -+ Added tutorial app `HelloMotor`. -+ Built using Gradle v6.8.2 . -+ Based on: - + v6.4.2 of the Heart library, - + v0.6.2 of the Wes library, - + v0.9.2 of the jme3-utilities-ui library, and - + v0.9.18 of the jme3-utilities-nifty library. -+ Updated the native libraries to v10.1.0 of Libbulletjme, which includes - Bullet v2 updates through 23 January 2021. - -## Version 3.1.0 released on 9 January 2021 - -+ Re-implemented `CollisionSpace.destroy()` to prepare a space for re-use. -+ Dump 7 more properties of each `VehicleWheel`. -+ Added the `TestIssue13` application. -+ Built using Gradle v6.8 . -+ Updated the native libraries to v9.3.2 of Libbulletjme, which includes - fixes for issues #12 and #13. - -## Version 3.1.0-test4 released on 6 January 2021 - -+ Bugfix: `CollisionShapeFactory.createBoxShape()` - positions the box incorrectly. -+ Added (experimental) support for the Linux_ARM32 platform with - "hf" (hardware floating-point support). -+ Added a `createMergedBoxShape()` method to the `CollisionShapeFactory` class. -+ Updated the native libraries to v9.3.1 of Libbulletjme. - -## Version 3.1.0-test3 released on 5 January 2021 - -+ Bugfix: off-by-one in validation of wheel indices -+ Added (experimental) support for the Linux_ARM32 platform. -+ Added a `createMergedHullShape()` method - to the `CollisionShapeFactory` class. -+ Publicized the `addMultiBody()` and `removeMultiBody()` methods - in the `MultiBodySpace` class. -+ Minimized usage of `PhysicsSpace.add()` and `PhysicsSpace.remove()`. -+ In `HelloPoi`, put the indicator in the `Translucent` bucket. -+ Built using Gradle v6.7.1 . -+ Based on: - + v6.2.0 of the Heart Library, - + v0.9.1 of the jme3-utilities-ui library, - + v0.9.17 of the jme3-utilities-nifty library. -+ Updated the native libraries to v9.3.0 of Libbulletjme. - -## Version 3.1.0-test2 released on 14 November 2020 - -+ Updated the native libraries to v9.2.3 of Libbulletjme (minSdkVersion=22). -+ Upgraded to JUnit v4.13.1 . - -## Version 3.1.0-test1 released on 14 November 2020 - -+ Updated the native libraries to v9.2.3 of Libbulletjme (Android SDK v30). -+ Added 3 compatibility methods: - + `BetterCharacterControl.getVelocity()` - + `BetterCharacterControl.getViewDirection()` - + `PhysicRayTestResult.getHitNormalLocal()` -+ Added tutorial app `HelloPoi`. -+ Built using Gradle v6.7 . -+ Based on v0.6.1 of the Wes Library. - -## Version 3.0.0 released on 31 August 2020 - -+ Bug fixes: - + collision-group checks are ineffective due to missing parentheses - + `ConcurrentModificationException` thrown by the "Physics Cleaner" thread - + assertion failures while tracking the ID of a soft-body anchor - + JVM crash while reading the collision flags of a static rigid body - + assertion failure during `PhysicsLink.postTick()` -+ Changes to the library API: - + changed the semantics of the `countJoints()` and `listJoints()` methods - in the `PhysicsBody` class - + changed the return type of the `rayTestRaw()` method - in the `CollisionSpace` class -+ New `DynamicAnimControl` features: - + a mechanism to ignore collisions between physics links - that aren't directly joined - + a mechanism to apply bone rotations (from an `AnimClip`, for instance) - to linked bones in dynamic mode - + `setLocalTransform()` methods for the managed bones of physics links - + a `fixToWorld()` method to lock a `PhysicsLink` into position - + an adjustable `pinToWorld()` method -+ Improvements to `New6Dof` constraints: - + enable springs in `RangeOfMotion` for more effective locking of axes - + a `NewHinge` subclass inspired by Bullet's `btHinge2Constraint` - + getters for the calculated transforms -+ More convenience: - + added a factory method to construct a satisfied, double-ended `New6Dof` - constraint using physics-space coordinates (instead of local ones) - + added a `clearIgnoreList()` method to the `PhysicsCollisionObject` class - + added a `CcdFilter` class to select rigid bodies with CCD active - + added a `DacUserFilter` class to select physics objects belonging to - a particular `DynamicAnimControl` - + added `findEnd()` and `findOtherBody()` methods to the `PhysicsJoint` class - + publicized the `boneIndex()` methods of `BoneLink` and `TorsoLink` - + construct a `RangeOfMotion` (for a fixed joint) using Euler angles -+ Improvements to `PhysicsDumper`: - + dump the positions of locked DoFs in a `New6Dof` - + an option to dump the ignored PCOs of a `PhysicsCollisionObject` - + dump the application data of a `PhysicsCollisionObject` -+ Built using Gradle v6.6.1 . -+ Based on: - + v6.0.0 of the Heart Library, - + v0.9.0 of the jme3-utilities-ui library, - + v0.6.0 of the Wes Library, and - + v0.9.15 of the jme3-utilities-nifty library. -+ Updated the native libraries to v9.2.2 of Libbulletjme. - -## Version 2.0.1 released on 8 August 2020 - -Bugfix: characters and ghosts ignore their own ignore lists! - -## Version 2.0.0 released on 22 July 2020 - -+ Bugfix: issue #9 (native crashes caused by invoking `finalizeNative()` - outside of the "Physics Cleaner" thread) -+ Bugfix: race condition during the removal of an `NpoTracker`. -+ Bugfix: issue #10 (native IDs of soft physics joints shouldn't be tracked) -+ Bugfix: location not initialized when creating a `PhysicsCharacter`. -+ Added 2 new constructors to the `HeightfieldCollisionShape` class. -+ Added argument validation to the `setMaxSlope()` method - in the `PhysicsCharacter` class. -+ Made more progress constructing the website. -+ Added 10 more tutorial apps. -+ Enabled gamma correction in tutorial apps that use lighting. -+ Updated the native libraries to v8.4.0 of Libbulletjme. - -## Version 2.0.0-test2 released on 4 July 2020 - -+ Added collision listeners for ongoing contacts, to the `PhysicsSpace` class. -+ Added `totalAppliedForce()` and `totalAppliedTorque()` methods - to the `PhysicsRigidBody` class. -+ Added a `setContactCalcArea3Points()` method - to the `PhysicsCollisionEvent` class. -+ Added 3 compatibility methods to the `PhysicsCollisionEvent` class. -+ Made progress building the website. -+ Added 7 more tutorial apps. -+ Dump additional information for rigid bodies. -+ Improved descriptions of user objects. -+ Built using Gradle v6.5.1 . -+ Updated the native libraries to v8.3.0 of Libbulletjme. - -## Version 2.0.0-test1 released on 19 June 2020 - -+ Changes to the library API: - + Deleted the `MinieCharacterControl` and `VHACDResults` classes. - + Replaced inner class `PhysicsSoftBody.Material` with `SoftBodyMaterial`. - + Deleted deprecated methods and constructors. - + De-publicized 7 debug-control classes. - + Changed arguments of the `PhysicsCharacter`, `CharacterControl`, - and `Convex2dShape` constructors from `CollisionShape` to `ConvexShape`. - + Changed an argument of a `HullCollisionShape` constructor to `float...`. - + Changed the `shape` arguments of `sweepTest()` methods to `ConvexShape`. - + Changed an argument of the `volumeConvex()` method to `ConvexShape`. - + Privatized 6 fields: - + `CollisionSpace.physicsSpaceTL` - + `CompoundMesh.scale` - + `PhysicsSpace.physicsJoints` - + `PhysicsSpace.pQueue` - + `VHACD.indices` - + `VHACD.vertices` - + Changed 7 returned collections to unmodifiable collections. - + Based the `PhysicsCollisionObject` and `PhysicsCollisionEvent` classes - on the `NativePhysicsObject` class. - + Added a private constructor to the `VHACD` class. - -+ Eliminated all `finalize()` methods by implementing a cleaner thread and - adding 4 classes: - + `BoundingValueHierarchy` - + `CharacterController` - + `NpoTracker` - + `VehicleController` -+ Added a GitHub Pages website, including javadoc and 3 new tutorial pages. -+ Implemented debug visualization for local physics. -+ Added 8 more tutorial apps. -+ Added the `TestEmptyShape` automated test. -+ Added accessors for the speculative contact restitution flag - of a `PhysicsSpace`. -+ Upgraded to Gradle v6.4.1, Libbulletjme v8.1.0, and JUnit v4.13 . - -## Version 1.7.0 released on 31 May 2020 - -+ Fixed bugs in the library: - + kinematic `PhysicsRigidBody` cloned as a dynamic one - + `CompoundCollisionShape.correctAxes()` yields incorrect results when - multiple children reference the same shape - + native objects of `SoftBodyWorldInfo` and `VehicleTuning` are never freed - + `worldInfo` field of `PhysicsSoftBody` not cloned, loaded, or saved - + `FINE` logging of collision spaces reports `nativeId=0` in `create()` - -+ Fixed bugs in applications: - + `SliderJoint` destroyed multiple times in `TestAttachDriver` - + `NullPointerException` thrown in `TestRbc` - + "childColoring" action not handled in `DropTest` - -+ Deprecated many obsolete methods slated to be removed from v2. - -+ Added debug-visualization features: - + rebuild the debug shape of a `CompoundCollisionShape` ONLY when it changes - + arrows to visualize cluster/node/rigid body velocity vectors - + markers to visualize pinned soft-body nodes - + arrows to visualize rigid/soft body gravity vectors - + add texture coordinates when visualizing a `PlaneCollisionShape` - + specify line widths for `PhysicsJoint` debug arrows - + configure the shadow mode of the debug root node - -+ Additional `PhysicsDumper` output: - + restitution of each rigid body - + angular velocity of each dynamic rigid body - + split-impulse parameters of each `SolverInfo` - + `isGravityProtected` for rigid bodies and `isWorldInfoProtected` - for soft bodies - + native ID for each `SoftBodyWorldInfo` - -+ Other added library features: - + an application-specific data reference for each `PhysicsCollisionObject` - + an ignore list for each `PhysicsCollisionObject` - + contact tests for collision spaces - + an option to protect the gravity of a `PhysicsRigidBody` from modification - by a `PhysicsSpace` - + an option to protect the world info of a `PhysicsSoftBody` from replacement - by a `PhysicsSoftSpace` - + methods to rotate/translate a `CompoundCollisionShape` - + a method to activate all collision objects in a `PhysicsSpace` - + publicized the `addJoint()` and `removeJoint()` methods of `PhysicsSpace` - + keep track of the `PhysicsSpace` (if any) to which each `PhysicsJoint` - is added - + construct an `IndexedMesh` from the debug mesh of a `CollisionShape` - + construct a `MeshCollisionShape` from a collection of native meshes - + a method to copy the cluster velocities of a `PhysicsSoftBody` - + getters for the combined rolling/spinning friction - of a `PhysicsCollisionEvent` - + access the split-impulse parameters of a `SolverInfo` - + `nativeId()` methods for `PhysicsCollisionEvent` and - `PhysicsCollisionObject`, to prepare for v2 - + getters for the proxy group and proxy mask of a `PhysicsCollisionObject` - + construct a `CompoundCollisionShape` with specified initial capacity - -+ New applications added: - + `TargetDemo`, a shooting demo - + `PoolDemo`, an eight-ball pool simulation with moody lighting - + `NewtonsCradle`, a Newton's cradle simulation - + `TestScaleChange` - + tests for JME issues 1283 and 1351 - + 3 apps that were missing from the Jme3Examples subproject - -+ Improvements to the `DropTest` application: - + use the PgUp key to "pop" the selected drop (if any) - + use the slash key to toggle between lit and wireframe materials - + when adding a Drop, avoid contact with existing rigid bodies - + added soft-body drop types (cloth and squishyBall) - + added jointed drop types (breakableRod, chain, diptych, flail, and ragdoll) - + added fixed-shape drop types (ankh, banana, barrel, bowl, bowlingPin, - horseshoe, iBeam, lidlessBox, link, snowman, table, thumbtack, - triangularFrame, trident, and washer) - + added "corner" and "square" platforms - + made gravity configurable - + applied a repeating texture to the "plane" platform visualization - -+ Other improvements to existing applications: - + minimized the hotkey help node initially - + added the "BaseMesh" model to various demos - + bound the "G" key to `System.gc()` in various demos - + disabled audio rendering in all apps that use `AppSettings` - -+ Major refactoring efforts: - + many classes based on a new `NativePhysicsObject` class - + many demo apps based on a new `AbstractDemo` class - + 4 debug controls based on a new `CollisionShapeDebugControl` class - + physics appstate configuration using a new `DebugConfiguration` class - -+ Added 5 more models with CC0 licenses ("Ankh", "Banana", "Barrel", - "BowlingPin", and "Horseshoe"). -+ Updated the native libraries to v6.4.0 of Libbulletjme. -+ Based on: - + the 3.3.2-stable release of jMonkeyEngine, - + v5.5.0 of the Heart Library, - + v0.8.3 of the jme3-utilities-ui library, and - + v0.5.0 of the Wes Library. -+ Built using Gradle v6.4.1 . - -## Version 1.6.1 released on 28 April 2020 - -Fixed JME issue 1351 (crash during garbage collection) - -## Version 1.6.0 released on 12 April 2020 - -+ Fixed bugs: - + `UnsatisfiedLinkError` on older Linux systems - "libstdc++.so.6: version `CXXABI_1.3.8’ not found" - + issue #2: certain soft-body methods cause access violations - under Java 9+ on Windows systems - + `NullPointerException` when re-adding a `PhysicsControl` that's already - added to a `Spatial` - + `rebuildRigidBody()` relied on static per-thread references to determine - the body's `PhysicsSpace` - + `NullPointerException` in `DacWizard` while editing the shape scale - of a `PhysicsLink` - -+ Added library features: - + support for 4 Android platforms (arm64-v8a, armeabi-v7a, x86, and x86_64) - + support for the 64-bit Linux-on-ARM platform (aarch64) - + `DynamicAnimControl` ignores spatials tagged with "JmePhysicsIgnore" - + `getCollisionSpace()` and `spaceId()` methods - to the `PhysicsCollisionObject` class - + `getRotationAngle()`, `setRotationAngle()`, `getSuspensionLength()`, - and `setSuspensionLength()` methods to the `VehicleWheel` class - + a `listDacMeshes()` method to the `RagUtils` class - + an `appendFromNativeMesh()` method to the `NativeSoftBodyUtil` class - + a `PcoType` class - + a `getFlags()` method to the `PhysicsCollisionEvent` class - and also a `ContactPointFlag` class - + `copyIndices()` and `copyVertexPositions()` methods - to the `IndexedMesh` class - + a `serializeBvh()` method to the `MeshCollisionShape` class and also a - constructor that takes serialized BVH - -+ Improvements to the `DacWizard` application: - + highlight the selected `PhysicsLink' in the Test screen - + click RMB to pick a `PhysicsLink` in the Test screen - + button in the Test screen to visualize the axes of the selected `BoneLink` - + button in the Test screen to save the model to a J3O file - + button in the Links screen to configure the `RotationOrder` of a `BoneLink` - + button in the Bones screen to bypass RoM estimation if the model already - has a DAC with the exact same linked bones - + dialog in the Test screen to adjust collision margins - + the "B"/PgUp and "N"/PgDn keys navigate between screens - + buttons in the Load and Test screens to visualize skeletons - + warn if there are multiple DACs in the model - + dark grey background - -+ Added most of the physics examples from `jme3-examples` - to the Jme3Examples subproject. -+ Added a `TestDebugToPost` application to the MinieExamples subproject. -+ Added build-command options for double-precision and debug-ready versions - of the library. -+ Reduced memory usage by reimplementing `IndexedMesh` using an `IndexBuffer`. -+ Customized the `RotationOrder` parameters of the sample DAC tunings. -+ Eliminated some non-standard collision margins - from the MinieExamples subproject. -+ Removed all references to the CesiumMan model. -+ Updated the native libraries to version 5.5.7 of `Libbulletjme`. -+ Based on: - + the 3.3.0-stable release of jMonkeyEngine, - + v5.2.1 of the `Heart` library, - + v0.8.2 of the `jme3-utilities-ui` library, - + v0.9.14 of the `jme3-utilities-nifty` library, and - + v0.4.9 of the `Wes` library. -+ Built using Gradle v6.3 . - -## Version 1.5.0for33 released on 12 March 2020 - -+ Fixed bugs: - + `NullPointerException` in the `DacWizard` application - + compound shapes read from J3O assets always get the default margin - + meshes returned by `DebugShapeFactory.getDebugMesh()` have incorrect bounds - + Minie issue #3: `btAssert()` crash at the peak of a character's jump - (only with a debug library) - -+ Added library features: - + `CharacterControl` class (for compatibility with jme3-bullet) - + 2 more `PhysicsSpace` constructors (for compatibility with jme3-bullet) - + new option for physics links: use `New6Dof` instead of `SixDofJoint` - + `CollisionSpace` class (for collision detection without dynamics) - + 4 more contact-and-constraint solvers for `PhysicsSpace` - + 3 more solver parameters: global CFM, minimum batch, and mode flags - + (experimental) support for multibody physics objects - + `ConvexShape` abstract subclass of `CollisionShape` - + new option for debug-mesh normals: sphere (radial) normals - + new option to dump child collision shapes in detail - + native IDs are now optional in physics dumps - + dump a single `CollisionShape` - + miscellaneous methods: - + `BulletAppState.isRunning()` - + `CollisionShapeFactory.createMergedMeshShape()` - + `DebugShapeFactory.getDebugTriangles()` - + `PhysicsSpace.countCollisionListeners()` - + `PhysicsSpace.countTickListeners()` - + `RagUtils.validate(Armature)` - + `VHACDHull.clonePositions()` - + `VHACDParameters.hashCode()` - -+ Added more detail to `PhysicsCharacter` dumps. -+ Added validation for the angular limits of a `SixDofJoint`. -+ Updated the native libraries to version 5.0.0 of `Libbulletjme`. -+ Based on version 5.1 of the `Heart` library. -+ Built using Gradle v6.2.2 . -+ Continuous integration at TravisCI and GitHub. - -## Version 1.4.1for33 released on 12 February 2020 - -Fixed JME issue 1283 (CCD doesn't respect collision groups) - -## Version 1.4.0for33 released on 7 February 2020 - -+ Fixed bugs: - + scaling bugs in `CompoundShape` and `Convex2dShape` - + `MyShape.height()` returns wrong value for a `CylinderCollisionShape` - + `DebugShapeFactory` cache should use a `WeakHashMap` for better garbage - collection - + issues with `NativeSoftBodyUtil` - + soft-body debug geometries can't receive shadows - -+ Added library features: - + a V-HACD interface with progress listeners, based on JNI - (eliminates the dependency on v-hacd-java-bindings) - + array-based constructor for `IndexedMesh` - + ray tests and sweep tests return a part index and/or triangle index - for many collision shapes - + `maxRadius()` methods for collision shapes - + cleaner debug visualization of swept spheres - + a debug visualization option to color the children of a compound shape - + `countCollisionListeners()` and `countCollisionGroupListeners()` methods - for the `PhysicsSpace` class - + a `countPinnedNodes()` method for `PhysicsSoftBody` - + a `parseNativeId()` method for the `MyShape` class, to replace `parseId()` - + `countCachedMeshes()` and `maxDistance()` methods - for the `DebugShapeFactory` class - + `RayTestFlag` value to disable the heightfield accelerator - + dump the listener counts of a `PhysicsSpace` - -+ Improvements to the `DropTest` demo: - + redesigned the user interface: use fewer keys and also display - a pause indicator and counts of active bodies and cached meshes - + added platforms: bed of nails, dimpled sheet, rounded rectangle, - sieve, trampoline - + added drop (gem) shapes: dome, (gridiron) football, frame, half pipe, - letters of the alphabet, prism, pyramid, sword - + select a drop with RMB, delete or dump the selected drop - + after deleting a drop, activate any that were asleep - -+ changed the Maven groupId to "com.github.stephengold" -+ moved issue-oriented tests to a new package -+ moved the `jme3test` package to a new `Jme3Tests` sub-project -+ Updated the native libraries to version 3.0.4 of `Libbulletjme`. -+ Based on: - + v5.0 of the `Heart` library, - + v0.8.1 of the `jme3-utilities-ui` library, and - + v0.4.8 of the `Wes` library. -+ Built using Gradle v6.1.1 . - -## Version 1.3.0for33 released on 5 January 2020 - -+ Fixed bugs: - + `PlaneCollisionShape` never visualized. - + Buffer limits not set in `IndexedMesh`. - -+ Added library features: - + 2-D collision shapes: `Box2dShape` and `Convex2dShape` - + a `setIndexBuffers()` method for the `DebugShapeFactory` class - + dump the moments of inertia of dynamic rigid bodies - + `castRay()`, `forwardAxisIndex()`, `rightAxisIndex()`, and `upAxisIndex()` - methods for the `PhysicsVehicle` class - + `getBrake()`, `getEngineForce()`, and `getSteerAngle()` - methods for the `VehicleWheel` class - + a `copyVertices()` method for the `SimplexCollisionShape` class - + construct a `SimplexCollisionShape` from a `FloatBuffer` range - + construct a `CylinderCollisionShape` from radius, height, and axis - + a `getHeight()` method for the `CylinderCollisionShape` class - + a `setScale(float)` method for the `CollisionShape` class - + `addChildShape(CollisionShape)` and - `addChildShape(CollisionShape, float, float, float)` - methods for the `CompoundCollisionShape` class - + a `listVolumes()` method for the `MyShape` class - + `unscaledVolume()` methods for the `BoxCollisionShape`, - `ConeCollisionShape`, `CylinderCollisionShape`, `EmptyCollisionShape`, - and `SphereCollisionShape` classes - + `DumpFlags` values for `BoundsInSpatials` and `VertexData` - -+ Improvements to the `DropTest` demo: - + Added 7 platform options (compound, cone, cylinder, hull, mesh, plane, - and triangle). - + Added 11 gem-shape options (barbell, capsule, chair, duck, heart, - knucklebone, ladder, sphere, star, teapot, and top). - + Tuned shadow edges. - -+ Improvements to the `HeightfieldTest` demo: - + Combined the demo with `TestScaleChange` and renamed it to `TestRbc`. - + Added many test shapes. - + Added a status line. - + Vary the collision margin and scale. - + Cursor shape indicates whether raytest finds an object. - + Visualize Bullet's bounding box. - + Added a hotkey to toggle the world axes. - -+ Began using `createIndexBuffer()` to generate test meshes and V-HACD shapes, - in order to conserve memory. -+ Refactored `TestRectangularShape` to make it more similar to the demos. -+ Extended `TestDefaults` to cover the `PhysicsGhostObject`, - `PhysicsVehicle`, and `VehicleWheel` classes. -+ Updated the native libraries to version 2.0.19 of `Libbulletjme`. -+ Based on: - + the NEW 3.3.0-beta1 release of jMonkeyEngine, - + v4.3 of the `jme3-utilities-heart` library, - + v0.7.10 of the `jme3-utilities-ui` library, and - + v0.9.12 of the `jme3-utilities-nifty` library. - + v0.4.7 of the `Wes` library. - -## Version 1.2.0for33 released on 16 December 2019 - -+ Added a `New6Dof` constraint class, to eventually replace both - `SixDofJoint` and `SixDofSpringJoint`. Also added 4 associated classes: - `MotorParam`, `RotationOrder`, `RotationMotor`, and `TranslationMotor`. -+ Added a status line to the `SeJointDemo` application. -+ Changed the function of the Ins key in `SeJointDemo` and `TestDac`. -+ Updated the native libraries to version 2.0.17 of `Libbulletjme`. -+ Based on: - + version 3.3.0-alpha5 of jMonkeyEngine, - + version 4.1 of the `jme3-utilities-heart` library, - + version 0.7.8 of the `jme3-utilities-ui` library, and - + version 0.9.10 of the `jme3-utilities-nifty` library. - + version 0.4.5 of the `Wes` library. - -## Version 1.1.1for33 released on 9 December 2019 - -+ Fixed bugs: - + Crash due to a denormalized `Quaternion` in `TorsoLink`. - + "K" key doubly mapped in the `TestDac` application. -+ Added model validation to the `DacWizard` application. -+ Added screenshot capability to 9 demo apps. -+ Extended `TestDefaults` to verify defaults for soft-body configs - and materials. -+ Updated the native libraries to version 2.0.14 of `Libbulletjme`. -+ Based on: - + jMonkeyEngine version v3.3.0-beta1, which was later deleted! - + version 4.2 of the `jme3-utilities-heart` library, - + version 0.7.9 of the `jme3-utilities-ui` library, and - + version 0.9.11 of the `jme3-utilities-nifty` library. - + version 0.4.6 of the `Wes` library. -+ Built using Gradle v6.0.1 . - -## Version 1.1.0for33 released on 4 November 2019 - -+ Added 4 getters to the `SixDofSpringJoint` class. -+ Added 3 compatibility methods to the `VehicleWheel` class. -+ Added some assertions to the `PhysicsRayTestResult` class. -+ Added the "application" Gradle plugin to the `DacWizard` build script. -+ Updated the native libraries to version 2.0.12 of `Libbulletjme`. -+ Built using Gradle v5.6.4 . - -## Version 1.0.0for33 released on 8 October 2019 - -+ API changes: - + Based `BulletAppState` on `AbstractAppState` (JME issue 1178). - + Removed the `extrapolateTransform()` and `getPhysicsScale()` methods - from `PhysicsRigidBody`. - + Renamed the `getLocation()` and `getRotation()` methods of - `ChildCollisionShape`. - + Privatized the `objectId` fields of `CollisionShape` and `PhysicsJoint`. - + Privatized the `collisionShape` field of `PhysicsCollisionObject`. - + Privatized the `bodyA` and `bodyB` fields of `PhysicsJoint`. - + Privatized the `cfm`, `erp`, and `split` fields of `SoftPhysicsJoint`. - + Finalized the `getObjectId()` methods - of `CollisionShape`, `PhysicsCollisionObject`, and `PhysicsJoint`. - + Protected many constructors that shouldn't be invoked directly. - + Removed the `countDistinctVertices()` method from `DebugMeshCallback` -+ Fixed bugs: - + `DacLinks` attempts to link a bone with no vertices - + in `DynamicAnimControl`, armature joints remain animated in ragdoll mode - + in `BuoyDemo`, old skeleton visualization persists after model a change - + `NullPointerException` while de-serializing an `AbstractPhysicsControl` - + NPEs while serializing/de-serializing a `DynamicAnimControl` that's - not added to a Spatial - + `NullPointerException` while cloning a `SoftBodyControl` - + out-of-bounds exception in `DebugMeshCallback` for an empty debug mesh - + `SoftBodyDebugControl` doesn't resize debug meshes - + `RuntimeException` in `DacWizard` while loading a non-model J3O - + `NullPointerException` in `DacWizard` after loading a non-animated model - + `OtoOldAnim.j3o` asset contained an invalid `MatParamOverride` - + scaling and rotation bugs in `DacWizard` - + bind pose not applied in to models in `TrackDemo` and `WatchDemo` apps - + `RopeDemo` delete key cancels skeleton visualization -+ Added library features: - + `getSquaredSpeed()` and `setEnableSleep()` for `PhysicsRigidBody` - + `getActivationState()` for `PhysicsCollisionObject` - + `Activation` and `AfMode` classes - + `correctAxes()`, `principalAxes()`, and `setChildTransform()` - for `CompoundCollisionShape` - + `copyRotation()` and `copyTransform() methods for `ChildCollisionShape` - + `countMeshTriangles()` for `MeshCollisionShape` - + `isConvex()`, `isInfinite()`, `isNonMoving()`, and `isPolyhedral()` methods - for `CollisionShape` - + `getViewDirection()` for `MinieCharacterControl` - + `IndexedMesh` constructors handle `TriangleFan` and - `TriangleStrip` mesh types - + `SoftBodyControl` handles 4 more mesh types -+ Enhancements to `PhysicsDumper`: - + shape and group info of a `PhysicsCharacter` - + group, orientation, scale, and shape of a `PhysicsGhost` - + AABBs, activation state, damping, and friction of a `PhysicsRigidBody` - + ID of the `CollisionShape` of a `PhysicsRigidBody` - + wheels of a `PhysicsVehicle` - + describe a `PlaneCollisionShape` - + simplify descriptions of various shapes, especially compounds -+ Changes to `MultiSphereDemo`: - + Renamed to `DropTest`. - + Added box, compound, cone, cylinder, simplex, and V-HACD shapes. - + Changed the Ins key to add a single gem instead of a shower. - + Added a UI to tune damping and friction. - + Added a `HeightfieldCollisionShape` platform as an alternative. - + Randomized the initial orientation of each dynamic body. -+ Other improvements: - + Updated `DacWizard` and demo apps to work with the new animation system. - + Implemented `SoftJointDebugControl`. - + `MinieAssets` sub-project converts OgreXML and glTF assets to J3O format. - + Added the `ForceDemo` app. - + Added the `TestCollisionShapeFactory`, `TestIssue1120`, and - `TestPhysicsRayCast` apps from jme3-examples. - + Added a "go limp" action to the "puppetInSkirt" test of `TestSoftBody`. - + Avoid aliasing in `HeighfieldCollisionShape` constructors. - + Added "toggle axes" and "toggle boxes" hotkeys to various demo apps. - + Updated the native libraries to version 2.0.10 of `Libbulletjme`. - + Based on version 4.1 of the `jme3-utilities-heart` library, version - 0.7.8 of the `jme3-utilities-ui` library, and version 0.9.10 of the - `jme3-utilities-nifty` library. - + Built using Gradle v5.6.2 . - -release log continues at https://github.com/stephengold/Minie/blob/master/MinieLibrary/release-notes-pre10.md +# Release log for the Minie library, DacWizard, MinieExamples, and VhacdTuner + +## Version 7.7.0 released on 31 August 2023 + ++ Fixed bugs: + + `NullPointerException` after rebuilding a `DynamicAnimControl` + + unhelpful warnings while cloning a `PhysicsRigidBody` + + `SplitDemo` app crashes caused by attempting to rotate heightfields + ++ Added to the library: + + the `MinkowskiSum` collision shape + + the `ClassFilter` class, for filtering physics objects ++ Specified a more precise return type for `Convex2dShape.getBaseShape()`. ++ `DynamicAnimControl` now logs a warning if armature scaling exceeds 1%. + ++ Improvements to the `DacWizard` application: + + Re-scale the 3-D model for each pose. + + Added a "rebuild" action to test mode, bound to `KEY_F3`. ++ Improvements to the `MinieDump` application: + + Added command-line options to specify an asset root. + + Added help/usage hints. ++ Improvements to the `MinieExamples` applications: + + Added more shapes to `DropTest` and `SplitDemo`. + + Reduced the risk of fallthru in `DropTest`, by disabling contact filtering. + ++ Based on v3.6.1-stable of JMonkeyEngine and v8.7.0 of the Heart library. ++ Built using Gradle v8.3 . ++ Updated the native libraries to v18.5.2 of Libbulletjme. + +## Version 7.6.0 released on 3 June 2023 + ++ Bugfix: the gravity of a rigid body isn't read/written/cloned ++ Bugfix: ignore lists are never cloned for non-joined collision objects ++ Bugfix: `IllegalStateException` in `DropTest` while creating a ragdoll ++ Allow zero-mass kinematic rigid bodies. ++ Added the an "addLinksForTris" parameter to `SoftBodyControl`. + ++ Based on v8.6.0 of the Heart library. ++ Built using Gradle v8.1.1 . ++ Updated the native libraries to v18.3.0 of Libbulletjme. + +## Version 7.5.0 released on 13 April 2023 + ++ Bugfix: during cloning, Bullet's ignore list + gets out-of-synch with the JVM list ++ Added the `applyAllExceptIgnoreListTo()` method + to the `RigidBodySnapshot` class. ++ Made private fields `static` in the tutorial apps. ++ Built using Gradle v8.1.0 . + +## Version 7.4.0 released on 24 March 2023 + ++ Fixed bugs: + + `BetterCharacterControl` oscillates while unducking near an overhang + + `BetterCharacterController.isOnGround()` is unreliable + + `TestIssue18Heightfield` application throws an `IllegalArgumentException` + + excessive logging from the `TestIssue13` application ++ Implemented kinematic modes for `BetterCharacterControl` + and `JoinedBodyControl`. ++ Deprecated 7 library methods for obtaining native IDs: + + `CollisionShape.getObjectId()` + + `CollisionSpace.getSpaceId()` + + `PhysicsCollisionObject.getObjectId()` + + `PhysicsJoint.getObjectId()` + + `RigidBodyMotionState.getObjectId()` + + `RotationalLimitMotor.getMotor()` + + `TranslationalLimitMotor.getMotor()` ++ Based on: + + v3.6.0-stable of JMonkeyEngine, + + v3.0.0 of the jME-TTF library, + + v8.3.2 of the Heart library, + + v1.0.0 of the Acorus library, + + v0.7.5 of the Wes library, and + + v0.9.35 of the jme3-utilities-nifty library. + +## Version 7.3.0+for36 released on 3 March 2023 + ++ Fixed bugs: + + ignored collision objects may be garbage-collected prematurely + + adding a `DynamicAnimControl` to a spatial resets its armature + + `PhysicsCollisionObject.cloneIgnoreList()` throws a `NullPointerException` + + `BetterCharacterControl.setPhysicsLocation()` never updates the field + + tasks passed to `PhysicsSpace.enqueueOnThisThread()` never execute + + `TestAttachDriver` example doesn't reset properly (JME issue 1975) ++ Added the `JointedBodyControl` class. ++ Added 7 methods to the library: + + `CollisionSpace.isForceUpdateAllAabbs()` + + `CollisionSpace.setForceUpdateAllAabbs()` + + `DynamicAnimControl.blendToKinematicMode(KinematicSubmode, float, Transform)` + + `DynamicAnimControl.saveCurrentPose()` + + `DynamicAnimControl.setKinematicMode(KinematicSubmode)` + + `PhysicsCollisionObject.listIgnoredPcos()` + + `PhysicsCollisionObject.setIgnoreList(PhysicsCollisionObject[])` ++ Added the "reset" kinematic submode to `DynamicAnimControl`. ++ Publicized 3 library methods: + + a constructor for the `IndexedMesh` class + + `DacLinks.getTransformer()` + + `RagUtils.findMainJoint()` ++ Deprecated 2 library methods: + + `PhysicsCollisionObject.listIgnoredIds()` + + `PhysicsCollisionObject.setIgnoreList(long[])` ++ Added some runtime checks and strengthened others. ++ Made improvements to the DacWizard application: + + added the "torso" screen to select the torso's main bone + + added posing capability to the "load" and "test" screens + + fixed a bug that caused generation of syntactically incorrect Java code + + fixed a bug where the wrong main bone was used to estimate ranges of motion + + when generating Java code, provide a `configure()` method + + generate practical classnames for Java code ++ Based on: + + v3.6.0-beta3 of JMonkeyEngine, + + v1.6.0 of the SimMath library, + + v8.3.1+for36 of the Heart library, + + v0.9.18+for36 of the Acorus library, + + v0.7.3+for36 of the Wes library, and + + v0.9.34+for36 of the jme3-utilities-nifty library. ++ Built using Gradle v8.0.2 . ++ Updated the native libraries to v18.1.0 of Libbulletjme. + +## Version 7.2.0 released on 24 January 2023 + ++ Bugfix: `PhysicsCollisionObject.findInstance()` + creates a weak global reference that can never be deleted ++ Bugfix: `DynamicAnimControl.totalMass()` returns `NaN` even when + the control is added to a `Spatial` ++ Bugfix: weak global references in `PhysicsCollisionObject` and `MultiBody` + can never be deleted ++ Added 2 methods to the library: + + `CollisionSpace.jniEnvId()` + + `NativeLibrary.jniEnvId()` ++ Updated the native libraries to v17.5.4 of Libbulletjme. + +## Version 7.1.0 released on 16 January 2023 + ++ Added the capability to generate cylinder shapes in a ragdoll. ++ Publicized the `RagUtils.makeRectangularSolid()` method. ++ Added `DynamicAnimControl` tunings for a Mixamo rig. ++ Added the capability to display angles in degrees or radians in DacWizard. ++ Updated `DacWizard` and `VhacdTuner` to use v0.9.33 + of the jme3-utilities-nifty library. + +## Version 7.0.2 released on 2 January 2023 + ++ Bugfix: `NullPointerException` in `rebuildRigidBody()` while de-serializing + an old model ++ Bugfix: DacWizard doesn't write rotation orders to Java source code + +## Version 7.0.1 released on 1 January 2023 + +Bugfix: `NullPointerException` in `PhysicsCollisionObject.readPcoProperties()` + +## Version 7.0.0 released on 24 December 2022 + ++ API changes: + + Privatized `PhysicsCollisionObject.getCollisionFlags()` (a native method) + + Added the `static` qualifier to `PersistentManifolds.listPointIds()` + + Renamed the public logger in the `ConvexShape` class to avoid conflict. + + Added a 2nd argument to `PhysicsBody.cloneJoints()` + + Corrected the return type of `CharacterController.jmeClone()` + + Finalized 3 classes: + + `NativeSoftBodyUtil` + + `PhysicsRayTestResult` + + `PhysicsSweepTestResult` + ++ Bug fixes: + + `DynamicAnimControl` may pass illegal arguments to `MyMath.slerp()` + + assertion failure when `toString()` is invoked on a collision object + or physics joint with no native object assigned + + out-of-range exception upon re-entering DacWizard's "bones" screen + with a different model + + transforms are not updated for the `getCalculatedOriginA()` and + `getCalculatedOriginB()` methods in the `New6Dof` class + + `getPhysicsLocationDp()` and `getPhysicsRotationDp()` return incorrect + values for a soft body + + cloning bugs: + + physics joints are cloned inaccurately + + ignore lists are cloned inaccurately + + cloning or rebuilding a collision object results in + different collision flags + + the feedback parameter of a `Constraint` isn't cloned + + `DacLinks` incompletely cloned + + `BoneLink.tmpMatrix` is shared between clones + + serialization bugs: + + `NullPointerException` in `PreComposer.read()` + + `RigidBodyMotionState` is never serialized + + the pivot offsets of single-ended constraints + are de-serialized incorrectly + + `PhysicsLink.density` is never saved or loaded + + the feedback parameter of a `Constraint` is never saved or loaded + + the `bindTransform` and `preComposer` fields of a `DacLinks` + are never saved or loaded + ++ Publicized the `PhysicsLink.velocity()` method. ++ Added 9 double-precision setters: + + `CharacterController.warpDp()` + + `MultiBodyCollider.setPhysicsLocationDp()` + + `MultiBodyCollider.setPhysicsRotationDp(Matrix3d)` + + `PhysicsCharacter.setPhysicsLocationDp()` + + `PhysicsGhostObject.setPhysicsLocationDp()` + + `PhysicsGhostObject.setPhysicsRotationDp()` (2 signatures) + + `PhysicsRigidBody.setPhysicsRotationDp(Matrix3d)` + + `PhysicsSoftBody.setPhysicsLocationDp()` ++ Added 8 other double-precision methods: + + `CollisionShape.getScaleDp()` + + `CollisionSpace.rayTestDp()` + + `ManifoldPoints.getPositionWorldOnADp()` + + `ManifoldPoints.getPositionWorldOnBDp()` + + `PhysicsCollisionObject.getPhysicsRotationMatrixDp()` + + `RigidBodyMotionState.getLocationDp()` + + `RigidBodyMotionState.getOrientationMatrixDp()` + + `RigidBodyMotionState.getOrientationQuaternionDp()` ++ Added 6 other methods: + + `New6Dof.calculatedBasisA()` + + `New6Dof.calculatedBasisB()` + + `New6Dof.getRotationMatrix()` + + `PhysicsCollisionObject.collisionFlags()` + + `PhysicsDescriber.describeMatrix()` + + `PhysicsDumper.dump(PhysicsJoint, String)` + ++ Added `INFO`-level log messages + to the `New6Dof` and `PhysicsRigidBody` classes. ++ Made incremental improvements to the `PhysicsDumper` output format. ++ Strengthened argument validation. ++ Updated the native libraries to v17.4.0 of Libbulletjme. ++ Updated `DacWizard` and `VhacdTuner` to use v0.9.32 + of the jme3-utilities-nifty library. ++ Built using Gradle v7.6 . + +## Version 6.2.0 released on 13 November 2022 + ++ Added 3 methods: + + `PhysicsSpace.isCcdWithStaticOnly()` + + `PhysicsSpace.setCcdWithStaticOnly()` + + `NativeLibrary.countClampedCcdMotions()` ++ Updated the native libraries to v17.0.0 of Libbulletjme. ++ Based on v0.9.17 of the Acorus library. + +## Version 6.1.0 released on 1 October 2022 + ++ Bugfix: `PreComposer` isn't properly cloned or serialized ++ Added library support for dynamic collision-shape splitting: + + Added 8 public constructors: + + `ChildCollisionShape(Vector3f, CollisionShape)` + + `CompoundMesh(CompoundMesh)` + + `CompoundMesh(CompoundMesh, Vector3f)` + + `GImpactCollisionShape(CompoundMesh, Vector3f)` + + `HullCollisionShape(Vector3f...)` + + `IndexedMesh(FloatBuffer)` + + `MeshCollisionShape(boolean, CompoundMesh)` + + `MultiSphere(Vector3f[], float...)` + + Added 17 other public methods: + + `ChildCollisionShape.split()` + + `CollisionShape.aabbCenter()` + + `CollisionShape.canSplit()` + + `CollisionShape.scaledVolume()` + + `CollisionShape.toSplittableShape()` + + `CompoundCollisionShape.connectivityMatrix()` + + `CompoundCollisionShape.countGroups()` + + `CompoundCollisionShape.split()` + + `CompoundMesh.maxMin()` + + `CompoundMesh.split()` + + `ConvexShape.toHullShape()` + + `GImpactCollisionShape.split()` + + `HullCollisionShape.split()` + + `IndexedMesh.copyTriangle()` + + `IndexedMesh.maxMin()` + + `IndexedMesh.split()` + + `MeshCollisionShape.split()` ++ Other library enhancements: + + Added the `createGImpactShape()` method + to the `CollisionShapeFactory` class. + + Added the `pairTest()` method to the `CollisionSpace` class. + + Added the `countMeshTriangles()` to the `GImpactCollisionShape` class. ++ Added the `SplitDemo` and `SweepDemo` applications. ++ Enhanced the `ShapeGenerator` class to use diverse axes when generating + capsule, cone, and cylinder shapes. ++ Added the "teapotGi" collision shape to the MinieAssets project. ++ Based on v8.2.0 of the Heart library. ++ Updated the native libraries to v16.3.0 of Libbulletjme. + +## Version 6.0.1 released on 29 August 2022 + ++ Bugfix: `DacLinks` never re-enables hardware skinning (performance issue) ++ VhacdTuner GUI tweaks: + + Removed the "ACDMode" buttons. + + Added buttons to toggle the "async" setting. + + Added a button to quit ranking the newest test result. ++ Added glTF loading capability to DacWizard. + +## Version 6.0.0 released on 25 August 2022 + ++ Protected the no-arg constructors of 6 abstract classes. (API changes) ++ Bugfix: issue #30 (`NullPointerException` after removing + `DynamicAnimControl` from a `Spatial`) ++ Bugfix: I/O resources not safely closed in `VHACDParameters`. ++ Added V-HACD version 4 including the `Vhacd4`, `Vhacd4Parameters, `FillMode`, + and `Vhacd4Hull` classes plus a new `HullCollisionShape` constructor and a + new `CollisionShapeFactory` method. ++ Added the VhacdTuner sub-project. ++ Added the `toMap()` method to the `VHACDParameters` class. ++ Updated the native libraries to v16.1.0 of Libbulletjme. + +## Version 5.1.0 released on 6 August 2022 + ++ Bugfix: `PhysicsRigidBody.setInverseInertiaLocal()` and + `PhysicsRigidBody.updateMassProps()` don't update the world inertia tensor ++ Bugfix: when rebuilding a rigid body, many properties are lost ++ Added a `DynamicAnimControl` mechanism to report the completion + of a blend-to-kinematic operation. ++ Added the `CompletionListener` interface. ++ Added the `RigidBodySnapshot` class. ++ Added the `setIgnoreList()` method to the `PhysicsCollisionObject` class. ++ Added a simpler constructor to `PhysicsSoftSpace`. ++ Publicized the `rebuildRigidBody()` method. ++ Updated the native libraries to v16.0.0 of Libbulletjme. ++ Built using Gradle v7.5.1 . + +## Version 5.0.1 released on 2 August 2022 + ++ Bugfix: static rigid body misbehaves after being assigned a positive mass ++ Bugfix: `DynamicAnimControl` with `AnimComposer` exhibits glitches during + blends to kinematic mode ++ Bugfix: when rebuilding a rigid body, its ignore list is lost ++ Based on v8.1.0 of the Heart library. ++ Built using Gradle v7.5 . + +## Version 5.0.0 released on 11 July 2022 + ++ Replaced the "ano" build with "droid" build that includes Java classes. ++ Changes to the library API: + + Replaced the `DebugMeshNormals` enum with `MeshNormals` (from Heart). + + Protected the no-arg constructors of the `DacConfiguration` + and `DacLinks` classes. + + Protected the public constructors of 3 classes: + + `AbstractPhysicsDebugControl` + + `IKController` + + `SoftPhysicsJoint` + + Qualified 17 utility classes as `final`. + + Removed the `static` qualifier from the protected `createWireMaterial()` + method of the `BulletDebugAppState` class. + + Deleted the deprecated `setContractCalcArea3Points()` + method of the `PhysicsCollisionEvent` class. + ++ Library bugfixes: + + `PhysicsCharacter.onGround()` is unreliable (stephengold/Libbulletjme#18) + + `TorsoLink` continues writing the model transform + after a blend to kinematic completes + + `DynamicAnimControl` rebuilds the ragdoll for minute changes to bone scaling + + `TorsoLink` plays some bone animations, even in dynamic mode + + outdated constant values in `ConfigFlag` + + `DynamicAnimControl` is still marked "ready" + after removal from the `PhysicsSpace` + + `DebugMeshCallback.maxDistance()` modifies the vertex list + + `ConfigFlag.describe()` ignores 3 flags + ++ Other library improvements: + + Warn if the native library version differs from the expected version. + + Throw an exception in `AbstractPhysicsControl.jmeClone()` + if the control is added to a `PhysicsSpace`. + + Add capability to specify the main bone of a ragdoll, + which needn't be a root bone. + + Added accessors for global deactivation settings: + + `PhysicsBody.getDeactivationDeadline()` + + `PhysicsBody.isDeactivationEnabled()` + + `PhysicsBody.setDeactivationDeadline()` + + `PhysicsBody.setDeactivationEnabled()` + + Added debug visualizations of rigid-body angular velocities + and soft-body wind velocities. + + Added a "relative tolerance" parameter to `DynamicAnimControl`. + + Reimplemented `BulletDebugAppState` using `BaseAppState`. + + Added the `DeformableSpace` class that supports both multibodies + and soft bodies. + + Added `SDF_MDF` and `SDF_RDN` bitmasks to `ConfigFlag`. + ++ Added the `Pachinko` and `Windlass` apps to MinieExamples. ++ Added the `HelloGhost` and `HelloWind` apps to TutorialApps. ++ Updated the project URL in the POM. ++ Added 10 "package-info.java" files. ++ Based on: + + v8.0.0 of the Heart library, + + v0.7.2 of the Wes library, + + v0.9.16 of the Acorus library, and + + v0.9.30 of the jme3-utilities-nifty library. ++ Updated the native libraries to v15.2.1 of Libbulletjme. ++ Added the "checkstyle" plugin to the build. + +## Version 4.9.0 released on 2 May 2022 + ++ Eliminated the last dependency on JCenter! ++ Bugfix: `IllegalArgumentException` caused by slerps in `TorsoLink` ++ Changed `CollisionShapeFactory` to skip meshes without triangles. ++ Added the `GearJoint` class. ++ Changes to the apps: + + Bugfix: rigid body becomes deactivated in `HelloContactReponse` + + Bugfix: `get(Limits.TextureAnisotropy)` returns `null` on some platforms + + Renamed `TestDebugToPost` and moved it from MinieExamples to TutorialApps. + + Added `TestGearJoint` to MinieExamples. + + Added 3rd body to `HelloDeactivation` for a visual reference point. + + Added monkey-head test to `TestSoftBodyControl`. + + Added display-settings editors to `NewtonsCradle` and `RopeDemo`. + + Enabled window resizing for `DacWizard` and all apps in MinieExamples. ++ Based on: + + v3.5.2-stable of JMonkeyEngine, + + v1.5.0 of the SimMath library, + + v7.6.0 of the Heart library, + + v0.9.15 of the Acorus library, and + + v0.9.29 of the jme3-utilities-nifty library. ++ Built using Gradle v7.4.2 . ++ Updated the native libraries to v14.3.0 of Libbulletjme. + +## Version 4.8.1 released on 29 March 2022 + ++ Bugfix: issue #23 (access violations on 64-bit Windows) ++ Split off "TutorialApps" from the "MinieExamples" sub-project. ++ Based on: + + v0.9.11 of the Acorus library and + + v0.9.28 of the jme3-utilities-nifty library. ++ Updated the native libraries to v14.2.0 of Libbulletjme. + +## Version 4.8.0 released on 20 March 2022 + ++ Added native libraries for the MacOSX_ARM64 platform, + so that Minie applications can run on "Apple Silicon" Macs. ++ Created the "+ano" (Android natives only) build, for use in + Android signed bundles (for distribution via Google Store). ++ Added the `contactErp()`, `jointErp()`, `setContactErp()`, + and `setJointErp()` methods to the `SolverInfo` class, + to give applications better control over joint elasticity. ++ Based on v3.5.1-stable of JMonkeyEngine. ++ Updated the native libraries to v14.1.0 of Libbulletjme. + +## Version 4.7.1 released on 11 March 2022 + ++ Bugfix: `PhysicsCollisionEvent.getNormalWorldOnB()` returns wrong value ++ Bugfix: issue #20 (`btAssert` fails after `HingeJoint.setAngularOnly(true)`) ++ Added the `needsCollision()` method to the `CollisionSpace` class. + This method can be overridden for dynamic collision filtering. ++ Added the `ContactListener` interface for immediate handling of rigid-body + contacts. ++ Added the `ManifoldPoints` utility class to access the properties + of a contact point without instantiating a `PhysicsCollisionEvent`. ++ Added the `PersistentManifolds` utility class to access the properties + of a contact manifold. ++ Added 8 methods to the `PhysicsSpace` class: + + `addContactListener(ContactListener)` + + `countManifolds()` + + `listManifolds()` + + `onContactEnded(PhysicsCollisionObject, PhysicsCollisionObject, long)` + + `onContactProcessed(PhysicsCollisionObject, PhysicsCollisionObject, long)` + + `onContactStarted(PhysicsCollisionObject, PhysicsCollisionObject, long)` + + `removeContactListener(ContactListener)` + + `update(float, int, boolean, boolean, boolean)` + to enable callbacks to specific `ContactListener` methods ++ Deprecated the `PhysicsCollisionEvent.setContactCalcArea3Points()` method + in favor of the corresponding `ManifoldPoints` method. ++ Deleted placeholder files from class jars. ++ Added the `ConveyorDemo` and `JointElasticity` apps to MinieExamples. ++ Based on: + + v7.4.1 of the Heart library, + + v0.7.1 of the Wes library, + + v0.9.10 of the Acorus library, and + + v0.9.27 of the jme3-utilities-nifty library. ++ Built using Gradle v7.4.1 . ++ Updated the native libraries to v14.0.0 of Libbulletjme. + +## Version 4.6.1 released on 24 January 2022 + ++ Restored support for the MacOSX32 platform. ++ Based on: + + v3.5.0-stable of JMonkeyEngine, + + v7.2.0 of the Heart library, + + v0.6.8 of the Wes library, + + v0.9.6 of the jme3-utilities-ui library, and + + v0.9.24 of the jme3-utilities-nifty library. ++ Built using Gradle v7.3.3 . ++ Updated the native libraries to v12.7.1 of Libbulletjme, which + include the fix for bullet3 issue 4117. + +## Version 4.6.0 released on 4 December 2021 + ++ Bugfix: issue #19 (zero-thickness mesh shape crashes the JRE) ++ Dropped support for the MacOSX32 platform. ++ Based on v3.4.1-stable of JMonkeyEngine. ++ Changes to the examples: + + Solved 2 null-pointer exceptions in Jme3Examples. + + Added the `TestInsideTriangle` and `TestIssue19` apps to MinieExamples. + + Split off the `PhysicsDemo` class from `AbstractDemo`. + + Built Jme3Examples using Java 8 . ++ Added the `isInsideTriangle()` method to the `NativeLibrary` class. ++ Guarded some low-level logging code to improve efficiency. ++ Built using Gradle v7.3.1 . ++ Updated the native libraries to v12.6.0 of Libbulletjme, which includes + Bullet updates through 13 November 2021. + +## Version 4.5.0 released on 19 November 2021 + ++ Cached the methods that free native objects, to improve performance. ++ Added the `setPivotInB()` method to the `Anchor` class. ++ Added a mesh customization API to the `DebugShapeFactory` class, + to support (for example) debug materials that need barycentric coordinates. ++ Publicized the `worldMax()` and `worldMin()` methods + in the `BulletAppState` class. ++ Improvements to examples: + + Solved `UnsatisfiedLinkError` crashes in 5 apps. + + Added the `TestIssue18GImpact` app. + + Addressed JME issue 1630 in `TestBetterCharacter`. ++ Updated the native libraries to v12.5.0 of Libbulletjme, + which includes contact filtering for GImpact shapes. ++ Built using Gradle v7.3 . + +## Version 4.5.0-test1 released on 25 October 2021 + ++ Bugfix: invalid contact points for heightfield/mesh shapes (issue #18) ++ Added a flag to disable contact filtering on a per-shape basis. ++ Added accessors to the `CollisionSpace` class for the collision dispatcher's + "deterministic" option. ++ Made all native physics objects `Comparable` (for use in collections). ++ Improvements to examples: + + Added 2 tutorial apps for character physics: + `HelloWalkOtoBcc` and `HelloWalkOtoCc`. + + Added 2 tests for issue #18: + `TestIssue18Heightfield` and `TestIssue18Mesh`. + + Made some tutorial apps more comparable. + + Created a merged mesh shape for the table in `PoolDemo` (more efficient). + + Reduced the camera zoom speed in `PoolDemo` (to avoid overshooting). ++ Updated the native libraries to v12.4.1 of Libbulletjme. + +## Version 4.4.0 released on 1 October 2021 + ++ Added 10 double-precision accessors and created an API dependency + on v1.4.1 of Paul's SimMath library. ++ Added more flexible customization of chassis axes in `VehicleController`. ++ Bugfix: incompatibility with Java v7 ++ Added the `NegativeAppDataFilter` class (for obstructions). ++ Added the `listInternalJoints()` method to the `RagUtils` class. ++ Added the example app `HelloServo`. ++ Improved diagnostic messages in `DacWizard`. ++ Based on v7.1.0 of the Heart library and v0.9.23 of the + jme3-utilities-nifty library. ++ Updated the native libraries to v12.2.2 of Libbulletjme, which includes + Bullet updates through 20 September 2021. + +## Version 4.3.0 released on 22 August 2021 + ++ Bugfix: contact tests report events with positive separation distance ++ Bugfix: `NullPointerException` while previewing an erroneous model during + DacWizard step #2 (the "load" step) ++ Bugfix: pure virtual call in native libraries (issue #17) ++ Added support for Quickprof profiling of native code. ++ Added support for multithreaded physics spaces. ++ Added the `TowerPerformance` application for performance testing. ++ Tweaked the behavior of `PhysicsRigidBody.setKinematic()`. ++ Bypass the asset cache when loading models into DacWizard. ++ Updated the native libraries to v12.0.0 of Libbulletjme + (includes performance enhancements). ++ Based on: + + LWJGL v3 (to improve support for non-US keyboards), + + v7.0.0 of the Heart library, + + v0.6.7 of the Wes library, + + v0.9.5 of the jme3-utilities-ui library, and + + v0.9.22 of the jme3-utilities-nifty library. ++ Built using Gradle v7.2 . + +## Version 4.2.0 released on 24 June 2021 + ++ Bugfix: Libbulletjme issue #7 (GImpact contact tests always fail) ++ Dump the bounds and ignore list of each `PhysicsGhostObject`. ++ Added `hasClosest()` and `hasContact()` methods + to the `CollisionSpace` class. ++ Added a public `getShapeType()` method to the `CollisionShape` class. ++ Document the lack of collision detection between 3 concave shapes. ++ Use a migrated Jaime model in MinieExamples. ++ Updated the native libraries to v10.5.0 of Libbulletjme, which includes + Bullet v2 updates through 12 May 2021. ++ Built using Gradle v7.1 . + +## Version 4.1.1 released on 1 June 2021 + ++ Bugfix: issue #16 (MinieExamples uses deprecated classes) ++ Based on: + + v3.4.0-stable of JMonkeyEngine, + + v2.2.4 of the jme-ttf library, + + v6.4.4 of the Heart library, + + v0.6.6 of the Wes library, + + v0.9.4 of the jme3-utilities-ui library, and + + v0.9.20 of the jme3-utilities-nifty library. ++ Built using Gradle v7.0.2 . + +## Version 4.1.0+for34 released on 25 April 2021 + ++ Bugfix: `RagUtils.ignoreCollisions()` doesn't handle cycles correctly ++ Bugfix: `NullPointerException` while reading a `DacLinks` from an XML file ++ Added `setPivotInA()` and `setPivotInB()` methods to `Point2PointJoint`. ++ Added `HelloDoor` application to the MinieExamples subproject. ++ Updated the native libraries to v10.3.1 of Libbulletjme, which includes + Bullet v2 updates through 21 April 2021. ++ Based on: + + v3.4.0-beta1 of JMonkeyEngine, + + v6.4.3+for34 of the Heart library, + + v0.6.3+for34 of the Wes library, + + v0.9.3+for34 of the jme3-utilities-ui library, and + + v0.9.19+for34 of the jme3-utilities-nifty library. ++ Built using Gradle v7.0 . + +## Version 4.0.2 released on 23 February 2021 + ++ Bugfix: debug visualization not updated for kinematic rigid bodies ++ Updated the native libraries to v10.2.0 of Libbulletjme. + +## Version 4.0.1 released on 16 February 2021 + ++ Bugfix: stack overflow while cloning a `DynamicAnimControl` ++ Upgraded to JUnit v4.13.2 . + +## Version 4.0.0 released on 11 February 2021 + ++ Bug fixes: + + debug visualization of a body jitters relative to its `PhysicsControl`. + + `DacWizard` generates syntactically incorrect source code for some locales + + issue #14 (suspension lengths of a `PhysicsVehicle` are not initialized) + + `IllegalArgumentException` while cloning a `PhysicsVehicle` + in a `PhysicsSpace` ++ A change to the library API: + + Delete the `angularMomentum()` and `kineticEnergy()` methods + from the `MultiBody` class. ++ Published to MavenCentral instead of JCenter. ++ Added a `render()` method to the `AbstractPhysicsControl` class. ++ Added tutorial app `HelloMotor`. ++ Built using Gradle v6.8.2 . ++ Based on: + + v6.4.2 of the Heart library, + + v0.6.2 of the Wes library, + + v0.9.2 of the jme3-utilities-ui library, and + + v0.9.18 of the jme3-utilities-nifty library. ++ Updated the native libraries to v10.1.0 of Libbulletjme, which includes + Bullet v2 updates through 23 January 2021. + +## Version 3.1.0 released on 9 January 2021 + ++ Re-implemented `CollisionSpace.destroy()` to prepare a space for re-use. ++ Dump 7 more properties of each `VehicleWheel`. ++ Added the `TestIssue13` application. ++ Built using Gradle v6.8 . ++ Updated the native libraries to v9.3.2 of Libbulletjme, which includes + fixes for issues #12 and #13. + +## Version 3.1.0-test4 released on 6 January 2021 + ++ Bugfix: `CollisionShapeFactory.createBoxShape()` + positions the box incorrectly. ++ Added (experimental) support for the Linux_ARM32 platform with + "hf" (hardware floating-point support). ++ Added a `createMergedBoxShape()` method to the `CollisionShapeFactory` class. ++ Updated the native libraries to v9.3.1 of Libbulletjme. + +## Version 3.1.0-test3 released on 5 January 2021 + ++ Bugfix: off-by-one in validation of wheel indices ++ Added (experimental) support for the Linux_ARM32 platform. ++ Added a `createMergedHullShape()` method + to the `CollisionShapeFactory` class. ++ Publicized the `addMultiBody()` and `removeMultiBody()` methods + in the `MultiBodySpace` class. ++ Minimized usage of `PhysicsSpace.add()` and `PhysicsSpace.remove()`. ++ In `HelloPoi`, put the indicator in the `Translucent` bucket. ++ Built using Gradle v6.7.1 . ++ Based on: + + v6.2.0 of the Heart Library, + + v0.9.1 of the jme3-utilities-ui library, + + v0.9.17 of the jme3-utilities-nifty library. ++ Updated the native libraries to v9.3.0 of Libbulletjme. + +## Version 3.1.0-test2 released on 14 November 2020 + ++ Updated the native libraries to v9.2.3 of Libbulletjme (minSdkVersion=22). ++ Upgraded to JUnit v4.13.1 . + +## Version 3.1.0-test1 released on 14 November 2020 + ++ Updated the native libraries to v9.2.3 of Libbulletjme (Android SDK v30). ++ Added 3 compatibility methods: + + `BetterCharacterControl.getVelocity()` + + `BetterCharacterControl.getViewDirection()` + + `PhysicRayTestResult.getHitNormalLocal()` ++ Added tutorial app `HelloPoi`. ++ Built using Gradle v6.7 . ++ Based on v0.6.1 of the Wes Library. + +## Version 3.0.0 released on 31 August 2020 + ++ Bug fixes: + + collision-group checks are ineffective due to missing parentheses + + `ConcurrentModificationException` thrown by the "Physics Cleaner" thread + + assertion failures while tracking the ID of a soft-body anchor + + JVM crash while reading the collision flags of a static rigid body + + assertion failure during `PhysicsLink.postTick()` ++ Changes to the library API: + + changed the semantics of the `countJoints()` and `listJoints()` methods + in the `PhysicsBody` class + + changed the return type of the `rayTestRaw()` method + in the `CollisionSpace` class ++ New `DynamicAnimControl` features: + + a mechanism to ignore collisions between physics links + that aren't directly joined + + a mechanism to apply bone rotations (from an `AnimClip`, for instance) + to linked bones in dynamic mode + + `setLocalTransform()` methods for the managed bones of physics links + + a `fixToWorld()` method to lock a `PhysicsLink` into position + + an adjustable `pinToWorld()` method ++ Improvements to `New6Dof` constraints: + + enable springs in `RangeOfMotion` for more effective locking of axes + + a `NewHinge` subclass inspired by Bullet's `btHinge2Constraint` + + getters for the calculated transforms ++ More convenience: + + added a factory method to construct a satisfied, double-ended `New6Dof` + constraint using physics-space coordinates (instead of local ones) + + added a `clearIgnoreList()` method to the `PhysicsCollisionObject` class + + added a `CcdFilter` class to select rigid bodies with CCD active + + added a `DacUserFilter` class to select physics objects belonging to + a particular `DynamicAnimControl` + + added `findEnd()` and `findOtherBody()` methods to the `PhysicsJoint` class + + publicized the `boneIndex()` methods of `BoneLink` and `TorsoLink` + + construct a `RangeOfMotion` (for a fixed joint) using Euler angles ++ Improvements to `PhysicsDumper`: + + dump the positions of locked DoFs in a `New6Dof` + + an option to dump the ignored PCOs of a `PhysicsCollisionObject` + + dump the application data of a `PhysicsCollisionObject` ++ Built using Gradle v6.6.1 . ++ Based on: + + v6.0.0 of the Heart Library, + + v0.9.0 of the jme3-utilities-ui library, + + v0.6.0 of the Wes Library, and + + v0.9.15 of the jme3-utilities-nifty library. ++ Updated the native libraries to v9.2.2 of Libbulletjme. + +## Version 2.0.1 released on 8 August 2020 + +Bugfix: characters and ghosts ignore their own ignore lists! + +## Version 2.0.0 released on 22 July 2020 + ++ Bugfix: issue #9 (native crashes caused by invoking `finalizeNative()` + outside of the "Physics Cleaner" thread) ++ Bugfix: race condition during the removal of an `NpoTracker`. ++ Bugfix: issue #10 (native IDs of soft physics joints shouldn't be tracked) ++ Bugfix: location not initialized when creating a `PhysicsCharacter`. ++ Added 2 new constructors to the `HeightfieldCollisionShape` class. ++ Added argument validation to the `setMaxSlope()` method + in the `PhysicsCharacter` class. ++ Made more progress constructing the website. ++ Added 10 more tutorial apps. ++ Enabled gamma correction in tutorial apps that use lighting. ++ Updated the native libraries to v8.4.0 of Libbulletjme. + +## Version 2.0.0-test2 released on 4 July 2020 + ++ Added collision listeners for ongoing contacts, to the `PhysicsSpace` class. ++ Added `totalAppliedForce()` and `totalAppliedTorque()` methods + to the `PhysicsRigidBody` class. ++ Added a `setContactCalcArea3Points()` method + to the `PhysicsCollisionEvent` class. ++ Added 3 compatibility methods to the `PhysicsCollisionEvent` class. ++ Made progress building the website. ++ Added 7 more tutorial apps. ++ Dump additional information for rigid bodies. ++ Improved descriptions of user objects. ++ Built using Gradle v6.5.1 . ++ Updated the native libraries to v8.3.0 of Libbulletjme. + +## Version 2.0.0-test1 released on 19 June 2020 + ++ Changes to the library API: + + Deleted the `MinieCharacterControl` and `VHACDResults` classes. + + Replaced inner class `PhysicsSoftBody.Material` with `SoftBodyMaterial`. + + Deleted deprecated methods and constructors. + + De-publicized 7 debug-control classes. + + Changed arguments of the `PhysicsCharacter`, `CharacterControl`, + and `Convex2dShape` constructors from `CollisionShape` to `ConvexShape`. + + Changed an argument of a `HullCollisionShape` constructor to `float...`. + + Changed the `shape` arguments of `sweepTest()` methods to `ConvexShape`. + + Changed an argument of the `volumeConvex()` method to `ConvexShape`. + + Privatized 6 fields: + + `CollisionSpace.physicsSpaceTL` + + `CompoundMesh.scale` + + `PhysicsSpace.physicsJoints` + + `PhysicsSpace.pQueue` + + `VHACD.indices` + + `VHACD.vertices` + + Changed 7 returned collections to unmodifiable collections. + + Based the `PhysicsCollisionObject` and `PhysicsCollisionEvent` classes + on the `NativePhysicsObject` class. + + Added a private constructor to the `VHACD` class. + ++ Eliminated all `finalize()` methods by implementing a cleaner thread and + adding 4 classes: + + `BoundingValueHierarchy` + + `CharacterController` + + `NpoTracker` + + `VehicleController` ++ Added a GitHub Pages website, including javadoc and 3 new tutorial pages. ++ Implemented debug visualization for local physics. ++ Added 8 more tutorial apps. ++ Added the `TestEmptyShape` automated test. ++ Added accessors for the speculative contact restitution flag + of a `PhysicsSpace`. ++ Upgraded to Gradle v6.4.1, Libbulletjme v8.1.0, and JUnit v4.13 . + +## Version 1.7.0 released on 31 May 2020 + ++ Fixed bugs in the library: + + kinematic `PhysicsRigidBody` cloned as a dynamic one + + `CompoundCollisionShape.correctAxes()` yields incorrect results when + multiple children reference the same shape + + native objects of `SoftBodyWorldInfo` and `VehicleTuning` are never freed + + `worldInfo` field of `PhysicsSoftBody` not cloned, loaded, or saved + + `FINE` logging of collision spaces reports `nativeId=0` in `create()` + ++ Fixed bugs in applications: + + `SliderJoint` destroyed multiple times in `TestAttachDriver` + + `NullPointerException` thrown in `TestRbc` + + "childColoring" action not handled in `DropTest` + ++ Deprecated many obsolete methods slated to be removed from v2. + ++ Added debug-visualization features: + + rebuild the debug shape of a `CompoundCollisionShape` ONLY when it changes + + arrows to visualize cluster/node/rigid body velocity vectors + + markers to visualize pinned soft-body nodes + + arrows to visualize rigid/soft body gravity vectors + + add texture coordinates when visualizing a `PlaneCollisionShape` + + specify line widths for `PhysicsJoint` debug arrows + + configure the shadow mode of the debug root node + ++ Additional `PhysicsDumper` output: + + restitution of each rigid body + + angular velocity of each dynamic rigid body + + split-impulse parameters of each `SolverInfo` + + `isGravityProtected` for rigid bodies and `isWorldInfoProtected` + for soft bodies + + native ID for each `SoftBodyWorldInfo` + ++ Other added library features: + + an application-specific data reference for each `PhysicsCollisionObject` + + an ignore list for each `PhysicsCollisionObject` + + contact tests for collision spaces + + an option to protect the gravity of a `PhysicsRigidBody` from modification + by a `PhysicsSpace` + + an option to protect the world info of a `PhysicsSoftBody` from replacement + by a `PhysicsSoftSpace` + + methods to rotate/translate a `CompoundCollisionShape` + + a method to activate all collision objects in a `PhysicsSpace` + + publicized the `addJoint()` and `removeJoint()` methods of `PhysicsSpace` + + keep track of the `PhysicsSpace` (if any) to which each `PhysicsJoint` + is added + + construct an `IndexedMesh` from the debug mesh of a `CollisionShape` + + construct a `MeshCollisionShape` from a collection of native meshes + + a method to copy the cluster velocities of a `PhysicsSoftBody` + + getters for the combined rolling/spinning friction + of a `PhysicsCollisionEvent` + + access the split-impulse parameters of a `SolverInfo` + + `nativeId()` methods for `PhysicsCollisionEvent` and + `PhysicsCollisionObject`, to prepare for v2 + + getters for the proxy group and proxy mask of a `PhysicsCollisionObject` + + construct a `CompoundCollisionShape` with specified initial capacity + ++ New applications added: + + `TargetDemo`, a shooting demo + + `PoolDemo`, an eight-ball pool simulation with moody lighting + + `NewtonsCradle`, a Newton's cradle simulation + + `TestScaleChange` + + tests for JME issues 1283 and 1351 + + 3 apps that were missing from the Jme3Examples subproject + ++ Improvements to the `DropTest` application: + + use the PgUp key to "pop" the selected drop (if any) + + use the slash key to toggle between lit and wireframe materials + + when adding a Drop, avoid contact with existing rigid bodies + + added soft-body drop types (cloth and squishyBall) + + added jointed drop types (breakableRod, chain, diptych, flail, and ragdoll) + + added fixed-shape drop types (ankh, banana, barrel, bowl, bowlingPin, + horseshoe, iBeam, lidlessBox, link, snowman, table, thumbtack, + triangularFrame, trident, and washer) + + added "corner" and "square" platforms + + made gravity configurable + + applied a repeating texture to the "plane" platform visualization + ++ Other improvements to existing applications: + + minimized the hotkey help node initially + + added the "BaseMesh" model to various demos + + bound the "G" key to `System.gc()` in various demos + + disabled audio rendering in all apps that use `AppSettings` + ++ Major refactoring efforts: + + many classes based on a new `NativePhysicsObject` class + + many demo apps based on a new `AbstractDemo` class + + 4 debug controls based on a new `CollisionShapeDebugControl` class + + physics appstate configuration using a new `DebugConfiguration` class + ++ Added 5 more models with CC0 licenses ("Ankh", "Banana", "Barrel", + "BowlingPin", and "Horseshoe"). ++ Updated the native libraries to v6.4.0 of Libbulletjme. ++ Based on: + + the 3.3.2-stable release of jMonkeyEngine, + + v5.5.0 of the Heart Library, + + v0.8.3 of the jme3-utilities-ui library, and + + v0.5.0 of the Wes Library. ++ Built using Gradle v6.4.1 . + +## Version 1.6.1 released on 28 April 2020 + +Fixed JME issue 1351 (crash during garbage collection) + +## Version 1.6.0 released on 12 April 2020 + ++ Fixed bugs: + + `UnsatisfiedLinkError` on older Linux systems + "libstdc++.so.6: version `CXXABI_1.3.8’ not found" + + issue #2: certain soft-body methods cause access violations + under Java 9+ on Windows systems + + `NullPointerException` when re-adding a `PhysicsControl` that's already + added to a `Spatial` + + `rebuildRigidBody()` relied on static per-thread references to determine + the body's `PhysicsSpace` + + `NullPointerException` in `DacWizard` while editing the shape scale + of a `PhysicsLink` + ++ Added library features: + + support for 4 Android platforms (arm64-v8a, armeabi-v7a, x86, and x86_64) + + support for the 64-bit Linux-on-ARM platform (aarch64) + + `DynamicAnimControl` ignores spatials tagged with "JmePhysicsIgnore" + + `getCollisionSpace()` and `spaceId()` methods + to the `PhysicsCollisionObject` class + + `getRotationAngle()`, `setRotationAngle()`, `getSuspensionLength()`, + and `setSuspensionLength()` methods to the `VehicleWheel` class + + a `listDacMeshes()` method to the `RagUtils` class + + an `appendFromNativeMesh()` method to the `NativeSoftBodyUtil` class + + a `PcoType` class + + a `getFlags()` method to the `PhysicsCollisionEvent` class + and also a `ContactPointFlag` class + + `copyIndices()` and `copyVertexPositions()` methods + to the `IndexedMesh` class + + a `serializeBvh()` method to the `MeshCollisionShape` class and also a + constructor that takes serialized BVH + ++ Improvements to the `DacWizard` application: + + highlight the selected `PhysicsLink' in the Test screen + + click RMB to pick a `PhysicsLink` in the Test screen + + button in the Test screen to visualize the axes of the selected `BoneLink` + + button in the Test screen to save the model to a J3O file + + button in the Links screen to configure the `RotationOrder` of a `BoneLink` + + button in the Bones screen to bypass RoM estimation if the model already + has a DAC with the exact same linked bones + + dialog in the Test screen to adjust collision margins + + the "B"/PgUp and "N"/PgDn keys navigate between screens + + buttons in the Load and Test screens to visualize skeletons + + warn if there are multiple DACs in the model + + dark grey background + ++ Added most of the physics examples from `jme3-examples` + to the Jme3Examples subproject. ++ Added a `TestDebugToPost` application to the MinieExamples subproject. ++ Added build-command options for double-precision and debug-ready versions + of the library. ++ Reduced memory usage by reimplementing `IndexedMesh` using an `IndexBuffer`. ++ Customized the `RotationOrder` parameters of the sample DAC tunings. ++ Eliminated some non-standard collision margins + from the MinieExamples subproject. ++ Removed all references to the CesiumMan model. ++ Updated the native libraries to version 5.5.7 of `Libbulletjme`. ++ Based on: + + the 3.3.0-stable release of jMonkeyEngine, + + v5.2.1 of the `Heart` library, + + v0.8.2 of the `jme3-utilities-ui` library, + + v0.9.14 of the `jme3-utilities-nifty` library, and + + v0.4.9 of the `Wes` library. ++ Built using Gradle v6.3 . + +## Version 1.5.0for33 released on 12 March 2020 + ++ Fixed bugs: + + `NullPointerException` in the `DacWizard` application + + compound shapes read from J3O assets always get the default margin + + meshes returned by `DebugShapeFactory.getDebugMesh()` have incorrect bounds + + Minie issue #3: `btAssert()` crash at the peak of a character's jump + (only with a debug library) + ++ Added library features: + + `CharacterControl` class (for compatibility with jme3-bullet) + + 2 more `PhysicsSpace` constructors (for compatibility with jme3-bullet) + + new option for physics links: use `New6Dof` instead of `SixDofJoint` + + `CollisionSpace` class (for collision detection without dynamics) + + 4 more contact-and-constraint solvers for `PhysicsSpace` + + 3 more solver parameters: global CFM, minimum batch, and mode flags + + (experimental) support for multibody physics objects + + `ConvexShape` abstract subclass of `CollisionShape` + + new option for debug-mesh normals: sphere (radial) normals + + new option to dump child collision shapes in detail + + native IDs are now optional in physics dumps + + dump a single `CollisionShape` + + miscellaneous methods: + + `BulletAppState.isRunning()` + + `CollisionShapeFactory.createMergedMeshShape()` + + `DebugShapeFactory.getDebugTriangles()` + + `PhysicsSpace.countCollisionListeners()` + + `PhysicsSpace.countTickListeners()` + + `RagUtils.validate(Armature)` + + `VHACDHull.clonePositions()` + + `VHACDParameters.hashCode()` + ++ Added more detail to `PhysicsCharacter` dumps. ++ Added validation for the angular limits of a `SixDofJoint`. ++ Updated the native libraries to version 5.0.0 of `Libbulletjme`. ++ Based on version 5.1 of the `Heart` library. ++ Built using Gradle v6.2.2 . ++ Continuous integration at TravisCI and GitHub. + +## Version 1.4.1for33 released on 12 February 2020 + +Fixed JME issue 1283 (CCD doesn't respect collision groups) + +## Version 1.4.0for33 released on 7 February 2020 + ++ Fixed bugs: + + scaling bugs in `CompoundShape` and `Convex2dShape` + + `MyShape.height()` returns wrong value for a `CylinderCollisionShape` + + `DebugShapeFactory` cache should use a `WeakHashMap` for better garbage + collection + + issues with `NativeSoftBodyUtil` + + soft-body debug geometries can't receive shadows + ++ Added library features: + + a V-HACD interface with progress listeners, based on JNI + (eliminates the dependency on v-hacd-java-bindings) + + array-based constructor for `IndexedMesh` + + ray tests and sweep tests return a part index and/or triangle index + for many collision shapes + + `maxRadius()` methods for collision shapes + + cleaner debug visualization of swept spheres + + a debug visualization option to color the children of a compound shape + + `countCollisionListeners()` and `countCollisionGroupListeners()` methods + for the `PhysicsSpace` class + + a `countPinnedNodes()` method for `PhysicsSoftBody` + + a `parseNativeId()` method for the `MyShape` class, to replace `parseId()` + + `countCachedMeshes()` and `maxDistance()` methods + for the `DebugShapeFactory` class + + `RayTestFlag` value to disable the heightfield accelerator + + dump the listener counts of a `PhysicsSpace` + ++ Improvements to the `DropTest` demo: + + redesigned the user interface: use fewer keys and also display + a pause indicator and counts of active bodies and cached meshes + + added platforms: bed of nails, dimpled sheet, rounded rectangle, + sieve, trampoline + + added drop (gem) shapes: dome, (gridiron) football, frame, half pipe, + letters of the alphabet, prism, pyramid, sword + + select a drop with RMB, delete or dump the selected drop + + after deleting a drop, activate any that were asleep + ++ changed the Maven groupId to "com.github.stephengold" ++ moved issue-oriented tests to a new package ++ moved the `jme3test` package to a new `Jme3Tests` sub-project ++ Updated the native libraries to version 3.0.4 of `Libbulletjme`. ++ Based on: + + v5.0 of the `Heart` library, + + v0.8.1 of the `jme3-utilities-ui` library, and + + v0.4.8 of the `Wes` library. ++ Built using Gradle v6.1.1 . + +## Version 1.3.0for33 released on 5 January 2020 + ++ Fixed bugs: + + `PlaneCollisionShape` never visualized. + + Buffer limits not set in `IndexedMesh`. + ++ Added library features: + + 2-D collision shapes: `Box2dShape` and `Convex2dShape` + + a `setIndexBuffers()` method for the `DebugShapeFactory` class + + dump the moments of inertia of dynamic rigid bodies + + `castRay()`, `forwardAxisIndex()`, `rightAxisIndex()`, and `upAxisIndex()` + methods for the `PhysicsVehicle` class + + `getBrake()`, `getEngineForce()`, and `getSteerAngle()` + methods for the `VehicleWheel` class + + a `copyVertices()` method for the `SimplexCollisionShape` class + + construct a `SimplexCollisionShape` from a `FloatBuffer` range + + construct a `CylinderCollisionShape` from radius, height, and axis + + a `getHeight()` method for the `CylinderCollisionShape` class + + a `setScale(float)` method for the `CollisionShape` class + + `addChildShape(CollisionShape)` and + `addChildShape(CollisionShape, float, float, float)` + methods for the `CompoundCollisionShape` class + + a `listVolumes()` method for the `MyShape` class + + `unscaledVolume()` methods for the `BoxCollisionShape`, + `ConeCollisionShape`, `CylinderCollisionShape`, `EmptyCollisionShape`, + and `SphereCollisionShape` classes + + `DumpFlags` values for `BoundsInSpatials` and `VertexData` + ++ Improvements to the `DropTest` demo: + + Added 7 platform options (compound, cone, cylinder, hull, mesh, plane, + and triangle). + + Added 11 gem-shape options (barbell, capsule, chair, duck, heart, + knucklebone, ladder, sphere, star, teapot, and top). + + Tuned shadow edges. + ++ Improvements to the `HeightfieldTest` demo: + + Combined the demo with `TestScaleChange` and renamed it to `TestRbc`. + + Added many test shapes. + + Added a status line. + + Vary the collision margin and scale. + + Cursor shape indicates whether raytest finds an object. + + Visualize Bullet's bounding box. + + Added a hotkey to toggle the world axes. + ++ Began using `createIndexBuffer()` to generate test meshes and V-HACD shapes, + in order to conserve memory. ++ Refactored `TestRectangularShape` to make it more similar to the demos. ++ Extended `TestDefaults` to cover the `PhysicsGhostObject`, + `PhysicsVehicle`, and `VehicleWheel` classes. ++ Updated the native libraries to version 2.0.19 of `Libbulletjme`. ++ Based on: + + the NEW 3.3.0-beta1 release of jMonkeyEngine, + + v4.3 of the `jme3-utilities-heart` library, + + v0.7.10 of the `jme3-utilities-ui` library, and + + v0.9.12 of the `jme3-utilities-nifty` library. + + v0.4.7 of the `Wes` library. + +## Version 1.2.0for33 released on 16 December 2019 + ++ Added a `New6Dof` constraint class, to eventually replace both + `SixDofJoint` and `SixDofSpringJoint`. Also added 4 associated classes: + `MotorParam`, `RotationOrder`, `RotationMotor`, and `TranslationMotor`. ++ Added a status line to the `SeJointDemo` application. ++ Changed the function of the Ins key in `SeJointDemo` and `TestDac`. ++ Updated the native libraries to version 2.0.17 of `Libbulletjme`. ++ Based on: + + version 3.3.0-alpha5 of jMonkeyEngine, + + version 4.1 of the `jme3-utilities-heart` library, + + version 0.7.8 of the `jme3-utilities-ui` library, and + + version 0.9.10 of the `jme3-utilities-nifty` library. + + version 0.4.5 of the `Wes` library. + +## Version 1.1.1for33 released on 9 December 2019 + ++ Fixed bugs: + + Crash due to a denormalized `Quaternion` in `TorsoLink`. + + "K" key doubly mapped in the `TestDac` application. ++ Added model validation to the `DacWizard` application. ++ Added screenshot capability to 9 demo apps. ++ Extended `TestDefaults` to verify defaults for soft-body configs + and materials. ++ Updated the native libraries to version 2.0.14 of `Libbulletjme`. ++ Based on: + + jMonkeyEngine version v3.3.0-beta1, which was later deleted! + + version 4.2 of the `jme3-utilities-heart` library, + + version 0.7.9 of the `jme3-utilities-ui` library, and + + version 0.9.11 of the `jme3-utilities-nifty` library. + + version 0.4.6 of the `Wes` library. ++ Built using Gradle v6.0.1 . + +## Version 1.1.0for33 released on 4 November 2019 + ++ Added 4 getters to the `SixDofSpringJoint` class. ++ Added 3 compatibility methods to the `VehicleWheel` class. ++ Added some assertions to the `PhysicsRayTestResult` class. ++ Added the "application" Gradle plugin to the `DacWizard` build script. ++ Updated the native libraries to version 2.0.12 of `Libbulletjme`. ++ Built using Gradle v5.6.4 . + +## Version 1.0.0for33 released on 8 October 2019 + ++ API changes: + + Based `BulletAppState` on `AbstractAppState` (JME issue 1178). + + Removed the `extrapolateTransform()` and `getPhysicsScale()` methods + from `PhysicsRigidBody`. + + Renamed the `getLocation()` and `getRotation()` methods of + `ChildCollisionShape`. + + Privatized the `objectId` fields of `CollisionShape` and `PhysicsJoint`. + + Privatized the `collisionShape` field of `PhysicsCollisionObject`. + + Privatized the `bodyA` and `bodyB` fields of `PhysicsJoint`. + + Privatized the `cfm`, `erp`, and `split` fields of `SoftPhysicsJoint`. + + Finalized the `getObjectId()` methods + of `CollisionShape`, `PhysicsCollisionObject`, and `PhysicsJoint`. + + Protected many constructors that shouldn't be invoked directly. + + Removed the `countDistinctVertices()` method from `DebugMeshCallback` ++ Fixed bugs: + + `DacLinks` attempts to link a bone with no vertices + + in `DynamicAnimControl`, armature joints remain animated in ragdoll mode + + in `BuoyDemo`, old skeleton visualization persists after model a change + + `NullPointerException` while de-serializing an `AbstractPhysicsControl` + + NPEs while serializing/de-serializing a `DynamicAnimControl` that's + not added to a Spatial + + `NullPointerException` while cloning a `SoftBodyControl` + + out-of-bounds exception in `DebugMeshCallback` for an empty debug mesh + + `SoftBodyDebugControl` doesn't resize debug meshes + + `RuntimeException` in `DacWizard` while loading a non-model J3O + + `NullPointerException` in `DacWizard` after loading a non-animated model + + `OtoOldAnim.j3o` asset contained an invalid `MatParamOverride` + + scaling and rotation bugs in `DacWizard` + + bind pose not applied in to models in `TrackDemo` and `WatchDemo` apps + + `RopeDemo` delete key cancels skeleton visualization ++ Added library features: + + `getSquaredSpeed()` and `setEnableSleep()` for `PhysicsRigidBody` + + `getActivationState()` for `PhysicsCollisionObject` + + `Activation` and `AfMode` classes + + `correctAxes()`, `principalAxes()`, and `setChildTransform()` + for `CompoundCollisionShape` + + `copyRotation()` and `copyTransform() methods for `ChildCollisionShape` + + `countMeshTriangles()` for `MeshCollisionShape` + + `isConvex()`, `isInfinite()`, `isNonMoving()`, and `isPolyhedral()` methods + for `CollisionShape` + + `getViewDirection()` for `MinieCharacterControl` + + `IndexedMesh` constructors handle `TriangleFan` and + `TriangleStrip` mesh types + + `SoftBodyControl` handles 4 more mesh types ++ Enhancements to `PhysicsDumper`: + + shape and group info of a `PhysicsCharacter` + + group, orientation, scale, and shape of a `PhysicsGhost` + + AABBs, activation state, damping, and friction of a `PhysicsRigidBody` + + ID of the `CollisionShape` of a `PhysicsRigidBody` + + wheels of a `PhysicsVehicle` + + describe a `PlaneCollisionShape` + + simplify descriptions of various shapes, especially compounds ++ Changes to `MultiSphereDemo`: + + Renamed to `DropTest`. + + Added box, compound, cone, cylinder, simplex, and V-HACD shapes. + + Changed the Ins key to add a single gem instead of a shower. + + Added a UI to tune damping and friction. + + Added a `HeightfieldCollisionShape` platform as an alternative. + + Randomized the initial orientation of each dynamic body. ++ Other improvements: + + Updated `DacWizard` and demo apps to work with the new animation system. + + Implemented `SoftJointDebugControl`. + + `MinieAssets` sub-project converts OgreXML and glTF assets to J3O format. + + Added the `ForceDemo` app. + + Added the `TestCollisionShapeFactory`, `TestIssue1120`, and + `TestPhysicsRayCast` apps from jme3-examples. + + Added a "go limp" action to the "puppetInSkirt" test of `TestSoftBody`. + + Avoid aliasing in `HeighfieldCollisionShape` constructors. + + Added "toggle axes" and "toggle boxes" hotkeys to various demo apps. + + Updated the native libraries to version 2.0.10 of `Libbulletjme`. + + Based on version 4.1 of the `jme3-utilities-heart` library, version + 0.7.8 of the `jme3-utilities-ui` library, and version 0.9.10 of the + `jme3-utilities-nifty` library. + + Built using Gradle v5.6.2 . + +release log continues at https://github.com/stephengold/Minie/blob/master/MinieLibrary/release-notes-pre10.md diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/MultiBodyJointType.java b/MinieLibrary/src/main/java/com/jme3/bullet/MultiBodyJointType.java index c208f8d03..3bf8c7138 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/MultiBodyJointType.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/MultiBodyJointType.java @@ -1,64 +1,64 @@ -/* - * Copyright (c) 2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet; - -/** - * Enumerate the types of joints in a MultiBody (native name: - * eFeatherstoneJointType). - * - * @author Stephen Gold sgold@sonic.net - */ -public enum MultiBodyJointType { - // ************************************************************************* - // values - - /** - * revolute joint (native name: eRevolute) - */ - Revolute, - /** - * prismatic joint (native name: ePrismatic) - */ - Prismatic, - /** - * spherical joint (native name: eSpherical) - */ - Spherical, - /** - * planar joint (native name: ePlanar) - */ - Planar, - /** - * fixed joint (native name: eFixed) - */ - Fixed -} +/* + * Copyright (c) 2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +/** + * Enumerate the types of joints in a MultiBody (native name: + * eFeatherstoneJointType). + * + * @author Stephen Gold sgold@sonic.net + */ +public enum MultiBodyJointType { + // ************************************************************************* + // values + + /** + * revolute joint (native name: eRevolute) + */ + Revolute, + /** + * prismatic joint (native name: ePrismatic) + */ + Prismatic, + /** + * spherical joint (native name: eSpherical) + */ + Spherical, + /** + * planar joint (native name: ePlanar) + */ + Planar, + /** + * fixed joint (native name: eFixed) + */ + Fixed +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/RotationOrder.java b/MinieLibrary/src/main/java/com/jme3/bullet/RotationOrder.java index 4b6125e9f..8918e852c 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/RotationOrder.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/RotationOrder.java @@ -1,100 +1,100 @@ -/* - * Copyright (c) 2019-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet; - -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import jme3utilities.Validate; - -/** - * Enumerate the orders in which axis rotations can be applied (native enum: - * RotateOrder). - * - * @author Stephen Gold sgold@sonic.net - */ -public enum RotationOrder { - // ************************************************************************* - // values - - /** - * X then Y then Z (native name: RO_XYZ) - */ - XYZ, - /** - * X then Z then Y (native name: RO_XZY) - */ - XZY, - /** - * Y then X then Z (native name: RO_YXZ) - */ - YXZ, - /** - * Y then Z then X (native name: RO_YZX) - */ - YZX, - /** - * Z then X then Y (native name: RO_ZXY) - */ - ZXY, - /** - * Z then Y then X (native name: RO_ZYX) - */ - ZYX; - // ************************************************************************* - // new methods exposed - - /** - * Convert a rotation matrix to Euler angles for this RotationOrder. - * - * @param rotMatrix the matrix to convert (not null, unaffected) - * @param storeResult storage for the result (modified if not null) - * @return the Euler angles (either storeResult or a new vector, not null) - */ - public Vector3f matrixToEuler(Matrix3f rotMatrix, Vector3f storeResult) { - Validate.nonNull(rotMatrix, "rot matrix"); - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - - int rotOrder = ordinal(); - /* - * matrixToEuler() returns false if the solution is not unique, - * but we ignore that information - */ - matrixToEuler(rotOrder, rotMatrix, result); - - return result; - } - // ************************************************************************* - // native private methods - - native private static boolean matrixToEuler( - int rotOrder, Matrix3f rotMatrix, Vector3f storeVector); -} +/* + * Copyright (c) 2019-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import jme3utilities.Validate; + +/** + * Enumerate the orders in which axis rotations can be applied (native enum: + * RotateOrder). + * + * @author Stephen Gold sgold@sonic.net + */ +public enum RotationOrder { + // ************************************************************************* + // values + + /** + * X then Y then Z (native name: RO_XYZ) + */ + XYZ, + /** + * X then Z then Y (native name: RO_XZY) + */ + XZY, + /** + * Y then X then Z (native name: RO_YXZ) + */ + YXZ, + /** + * Y then Z then X (native name: RO_YZX) + */ + YZX, + /** + * Z then X then Y (native name: RO_ZXY) + */ + ZXY, + /** + * Z then Y then X (native name: RO_ZYX) + */ + ZYX; + // ************************************************************************* + // new methods exposed + + /** + * Convert a rotation matrix to Euler angles for this RotationOrder. + * + * @param rotMatrix the matrix to convert (not null, unaffected) + * @param storeResult storage for the result (modified if not null) + * @return the Euler angles (either storeResult or a new vector, not null) + */ + public Vector3f matrixToEuler(Matrix3f rotMatrix, Vector3f storeResult) { + Validate.nonNull(rotMatrix, "rot matrix"); + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + + int rotOrder = ordinal(); + /* + * matrixToEuler() returns false if the solution is not unique, + * but we ignore that information + */ + matrixToEuler(rotOrder, rotMatrix, result); + + return result; + } + // ************************************************************************* + // native private methods + + native private static boolean matrixToEuler( + int rotOrder, Matrix3f rotMatrix, Vector3f storeVector); +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/SolverType.java b/MinieLibrary/src/main/java/com/jme3/bullet/SolverType.java index f8b5b948d..91c99d1d5 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/SolverType.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/SolverType.java @@ -1,71 +1,71 @@ -/* - * Copyright (c) 2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet; - -/** - * Enumerate the available contact-and-constraint solvers. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum SolverType { - // ************************************************************************* - // values - - /** - * btSequentialImpulseConstraintSolver/btMultiBodyConstraintSolver: Bullet's - * original sequential-impulse solver - */ - SI, - /** - * btDantzigSolver: Mixed Linear Complementarity Problem (MLCP) direct - * solver using the Dantzig Algorithm - */ - Dantzig, - /** - * btLemkeSolver: accurate-but-slow MLCP direct solver using Lemke’s - * Algorithm (see "Fast Implementation of Lemke’s Algorithm for Rigid Body - * Contact Simulation" by John E. Lloyd) - *

- * Seems to require a global CFM > 0. - */ - Lemke, - /** - * btSolveProjectedGaussSeidel: slow MLCP direct solver using projected - * Gauss-Seidel (PGS) for debug/learning purposes - */ - PGS, - /** - * btNNCGConstraintSolver: using the Non-smooth Nonlinear Conjugate Gradient - * (NNCG) method - */ - NNCG -} +/* + * Copyright (c) 2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet; + +/** + * Enumerate the available contact-and-constraint solvers. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum SolverType { + // ************************************************************************* + // values + + /** + * btSequentialImpulseConstraintSolver/btMultiBodyConstraintSolver: Bullet's + * original sequential-impulse solver + */ + SI, + /** + * btDantzigSolver: Mixed Linear Complementarity Problem (MLCP) direct + * solver using the Dantzig Algorithm + */ + Dantzig, + /** + * btLemkeSolver: accurate-but-slow MLCP direct solver using Lemke’s + * Algorithm (see "Fast Implementation of Lemke’s Algorithm for Rigid Body + * Contact Simulation" by John E. Lloyd) + *

+ * Seems to require a global CFM > 0. + */ + Lemke, + /** + * btSolveProjectedGaussSeidel: slow MLCP direct solver using projected + * Gauss-Seidel (PGS) for debug/learning purposes + */ + PGS, + /** + * btNNCGConstraintSolver: using the Non-smooth Nonlinear Conjugate Gradient + * (NNCG) method + */ + NNCG +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/CenterHeuristic.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/CenterHeuristic.java index bbbb245f8..655db72b4 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/CenterHeuristic.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/CenterHeuristic.java @@ -1,108 +1,108 @@ -/* - * Copyright (c) 2018-2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.animation; - -import com.jme3.bounding.BoundingSphere; -import com.jme3.math.Vector3f; -import java.nio.FloatBuffer; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.VectorSet; - -/** - * Enumerate algorithms to locate the center of mass for a PhysicsLink. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum CenterHeuristic { - // ************************************************************************* - // values - - /** - * center of the smallest axis-aligned bounding box - */ - AABB, - /** - * for bone links only: center on the joint's pivot - */ - Joint, - /** - * unweighted average of vertex locations - */ - Mean, - /** - * center of the smallest enclosing sphere (using Welzl's algorithm) - */ - Sphere; - // ************************************************************************* - // new methods exposed - - /** - * Calculate a center for the specified set of location vectors. No - * implementation for {@link #Joint}. - * - * @param locations the set of location vectors (not null, not empty, - * unaffected) - * @param storeResult storage for the result (modified if not null) - * @return a location vector (either storeResult or a new vector, not null) - */ - public Vector3f center(VectorSet locations, Vector3f storeResult) { - int numVectors = locations.numVectors(); - assert numVectors > 0 : numVectors; - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - - switch (this) { - case AABB: - Vector3f maxima = new Vector3f(); - Vector3f minima = new Vector3f(); - locations.maxMin(maxima, minima); - MyVector3f.midpoint(maxima, minima, result); - break; - - case Mean: - locations.mean(result); - break; - - case Sphere: - BoundingSphere boundingSphere = new BoundingSphere(); - FloatBuffer buffer = locations.toBuffer(); - boundingSphere.computeFromPoints(buffer); - boundingSphere.getCenter(result); - break; - - default: - String message = "heuristic = " + toString(); - throw new IllegalStateException(message); - } - - return result; - } -} +/* + * Copyright (c) 2018-2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.animation; + +import com.jme3.bounding.BoundingSphere; +import com.jme3.math.Vector3f; +import java.nio.FloatBuffer; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.VectorSet; + +/** + * Enumerate algorithms to locate the center of mass for a PhysicsLink. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum CenterHeuristic { + // ************************************************************************* + // values + + /** + * center of the smallest axis-aligned bounding box + */ + AABB, + /** + * for bone links only: center on the joint's pivot + */ + Joint, + /** + * unweighted average of vertex locations + */ + Mean, + /** + * center of the smallest enclosing sphere (using Welzl's algorithm) + */ + Sphere; + // ************************************************************************* + // new methods exposed + + /** + * Calculate a center for the specified set of location vectors. No + * implementation for {@link #Joint}. + * + * @param locations the set of location vectors (not null, not empty, + * unaffected) + * @param storeResult storage for the result (modified if not null) + * @return a location vector (either storeResult or a new vector, not null) + */ + public Vector3f center(VectorSet locations, Vector3f storeResult) { + int numVectors = locations.numVectors(); + assert numVectors > 0 : numVectors; + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + + switch (this) { + case AABB: + Vector3f maxima = new Vector3f(); + Vector3f minima = new Vector3f(); + locations.maxMin(maxima, minima); + MyVector3f.midpoint(maxima, minima, result); + break; + + case Mean: + locations.mean(result); + break; + + case Sphere: + BoundingSphere boundingSphere = new BoundingSphere(); + FloatBuffer buffer = locations.toBuffer(); + boundingSphere.computeFromPoints(buffer); + boundingSphere.getCenter(result); + break; + + default: + String message = "heuristic = " + toString(); + throw new IllegalStateException(message); + } + + return result; + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/IKController.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/IKController.java index 40fa2d152..4241e075d 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/IKController.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/IKController.java @@ -1,210 +1,210 @@ -/* - * Copyright (c) 2018-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.animation; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.util.clone.Cloner; -import com.jme3.util.clone.JmeCloneable; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; - -/** - * An abstract inverse kinematics (IK) controller for a PhysicsLink in dynamic - * mode. - * - * @author Stephen Gold sgold@sonic.net - */ -abstract public class IKController implements JmeCloneable, Savable { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(IKController.class.getName()); - /** - * field names for serialization - */ - final private static String tagControlledLink = "controlledLink"; - final private static String tagIsEnabled = "isEnabled"; - // ************************************************************************* - // fields - - /** - * true → enabled, false → disabled - */ - private boolean isEnabled; - /** - * PhysicsLink controlled by this controller - */ - private PhysicsLink controlledLink; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected IKController() { - } - - /** - * Instantiate an enabled controller. - * - * @param controlledLink the link to be controlled (not null) - */ - protected IKController(PhysicsLink controlledLink) { - assert controlledLink != null; - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Creating controller for bone {0}.", - MyString.quote(controlledLink.boneName())); - } - - this.controlledLink = controlledLink; - isEnabled = true; - } - // ************************************************************************* - // new methods exposed - - /** - * Access the controlled link. - * - * @return the pre-existing instance (not null) - */ - public PhysicsLink getLink() { - return controlledLink; - } - - /** - * Test whether this controller is enabled. - * - * @return the pre-existing instance (not null) - */ - public boolean isEnabled() { - return isEnabled; - } - - /** - * Apply forces, impulses, and torques to the rigid body. Invoked just - * before the physics is stepped. - * - * @param timeStep the physics time step (in seconds, ≥0) - */ - abstract public void preTick(float timeStep); - - /** - * Enable or disable this controller. - * - * @param desiredSetting true to enable, false to disable - */ - public void setEnabled(boolean desiredSetting) { - isEnabled = desiredSetting; - } - - /** - * Immediately put this controller into ragdoll mode. Unless overridden, - * this method simply disables the controller. - */ - public void setRagdollMode() { - isEnabled = false; - } - // ************************************************************************* - // JmeCloneable methods - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned controller into a deep-cloned one, using the specified - * Cloner and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this controller (not null) - * @param original the instance from which this controller was - * shallow-cloned (unused) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - this.controlledLink = cloner.clone(controlledLink); - } - - /** - * Create a shallow clone for the JME cloner. - * - * @return a new instance - */ - @Override - public IKController jmeClone() { - try { - IKController clone = (IKController) clone(); - return clone; - } catch (CloneNotSupportedException exception) { - throw new RuntimeException(exception); - } - } - // ************************************************************************* - // Savable methods - - /** - * De-serialize this controller from the specified importer, for example - * when loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - InputCapsule capsule = importer.getCapsule(this); - - isEnabled = capsule.readBoolean(tagIsEnabled, true); - controlledLink - = (PhysicsLink) capsule.readSavable(tagControlledLink, null); - } - - /** - * Serialize this controller to the specified exporter, for example when - * saving to a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - OutputCapsule capsule = exporter.getCapsule(this); - - capsule.write(isEnabled, tagIsEnabled, true); - capsule.write(controlledLink, tagControlledLink, null); - } -} +/* + * Copyright (c) 2018-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.animation; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; + +/** + * An abstract inverse kinematics (IK) controller for a PhysicsLink in dynamic + * mode. + * + * @author Stephen Gold sgold@sonic.net + */ +abstract public class IKController implements JmeCloneable, Savable { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(IKController.class.getName()); + /** + * field names for serialization + */ + final private static String tagControlledLink = "controlledLink"; + final private static String tagIsEnabled = "isEnabled"; + // ************************************************************************* + // fields + + /** + * true → enabled, false → disabled + */ + private boolean isEnabled; + /** + * PhysicsLink controlled by this controller + */ + private PhysicsLink controlledLink; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected IKController() { + } + + /** + * Instantiate an enabled controller. + * + * @param controlledLink the link to be controlled (not null) + */ + protected IKController(PhysicsLink controlledLink) { + assert controlledLink != null; + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Creating controller for bone {0}.", + MyString.quote(controlledLink.boneName())); + } + + this.controlledLink = controlledLink; + isEnabled = true; + } + // ************************************************************************* + // new methods exposed + + /** + * Access the controlled link. + * + * @return the pre-existing instance (not null) + */ + public PhysicsLink getLink() { + return controlledLink; + } + + /** + * Test whether this controller is enabled. + * + * @return the pre-existing instance (not null) + */ + public boolean isEnabled() { + return isEnabled; + } + + /** + * Apply forces, impulses, and torques to the rigid body. Invoked just + * before the physics is stepped. + * + * @param timeStep the physics time step (in seconds, ≥0) + */ + abstract public void preTick(float timeStep); + + /** + * Enable or disable this controller. + * + * @param desiredSetting true to enable, false to disable + */ + public void setEnabled(boolean desiredSetting) { + isEnabled = desiredSetting; + } + + /** + * Immediately put this controller into ragdoll mode. Unless overridden, + * this method simply disables the controller. + */ + public void setRagdollMode() { + isEnabled = false; + } + // ************************************************************************* + // JmeCloneable methods + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned controller into a deep-cloned one, using the specified + * Cloner and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this controller (not null) + * @param original the instance from which this controller was + * shallow-cloned (unused) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + this.controlledLink = cloner.clone(controlledLink); + } + + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ + @Override + public IKController jmeClone() { + try { + IKController clone = (IKController) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } + // ************************************************************************* + // Savable methods + + /** + * De-serialize this controller from the specified importer, for example + * when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); + + isEnabled = capsule.readBoolean(tagIsEnabled, true); + controlledLink + = (PhysicsLink) capsule.readSavable(tagControlledLink, null); + } + + /** + * Serialize this controller to the specified exporter, for example when + * saving to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(isEnabled, tagIsEnabled, true); + capsule.write(controlledLink, tagControlledLink, null); + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/KinematicSubmode.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/KinematicSubmode.java index 77b587b8d..2398e73b2 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/KinematicSubmode.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/KinematicSubmode.java @@ -1,60 +1,60 @@ -/* - * Copyright (c) 2018-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.animation; - -/** - * Enumerate submodes for a link in kinematic mode. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum KinematicSubmode { - /** - * amputated away - */ - Amputated, - /** - * driven by animation (if any) - */ - Animated, - /** - * forced into bind pose - */ - Bound, - /** - * frozen in the transform it had when blending started - */ - Frozen, - /** - * forced to the pose defined by {@code setEndBoneTransforms()} - */ - Reset -} +/* + * Copyright (c) 2018-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.animation; + +/** + * Enumerate submodes for a link in kinematic mode. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum KinematicSubmode { + /** + * amputated away + */ + Amputated, + /** + * driven by animation (if any) + */ + Animated, + /** + * forced into bind pose + */ + Bound, + /** + * frozen in the transform it had when blending started + */ + Frozen, + /** + * forced to the pose defined by {@code setEndBoneTransforms()} + */ + Reset +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/MassHeuristic.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/MassHeuristic.java index 9e2b842c2..a7464cf8f 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/MassHeuristic.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/MassHeuristic.java @@ -1,52 +1,52 @@ -/* - * Copyright (c) 2018-2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.animation; - -/** - * Enumerate algorithms to determine the mass for a PhysicsLink. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum MassHeuristic { - // ************************************************************************* - // values - - /** - * multiply the configured parameter by the unscaled volume of the - * CollisionShape - */ - Density, - /** - * use the configured parameter directly - */ - Mass -} +/* + * Copyright (c) 2018-2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.animation; + +/** + * Enumerate algorithms to determine the mass for a PhysicsLink. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum MassHeuristic { + // ************************************************************************* + // values + + /** + * multiply the configured parameter by the unscaled volume of the + * CollisionShape + */ + Density, + /** + * use the configured parameter directly + */ + Mass +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/RagUtils.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/RagUtils.java index 1245f5dc2..a23dfb059 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/RagUtils.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/RagUtils.java @@ -1,869 +1,869 @@ -/* - * Copyright (c) 2018-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.animation; - -import com.jme3.anim.Armature; -import com.jme3.anim.Joint; -import com.jme3.anim.SkinningControl; -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.Savable; -import com.jme3.math.Eigen3f; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.UserData; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.control.AbstractControl; -import java.io.IOException; -import java.nio.Buffer; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.logging.Logger; -import jme3utilities.MyMesh; -import jme3utilities.MySkeleton; -import jme3utilities.MySpatial; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyQuaternion; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.RectangularSolid; -import jme3utilities.math.VectorSet; -import jme3utilities.math.VectorSetUsingBuffer; - -/** - * Utility methods used by DynamicAnimControl and associated classes. - * - * @author Stephen Gold sgold@sonic.net - * - * Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). - */ -final public class RagUtils { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(RagUtils.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private RagUtils() { - } - // ************************************************************************* - // new methods exposed - - /** - * Assign each mesh vertex to a bone/torso link and add its location (mesh - * coordinates in bind pose) to that link's list. - *

- * A software skin update must precede any request for vertex locations. - * TODO use the Wes library to avoid this limitation? - * - * @param meshes array of animated meshes to use (not null, unaffected) - * @param managerMap a map from bone indices to managing link names (not - * null, unaffected) - * @return a new map from bone/torso names to sets of vertex coordinates - */ - public static Map - coordsMap(Mesh[] meshes, String[] managerMap) { - Validate.nonNull(managerMap, "manager map"); - - float[] wArray = new float[4]; - int[] iArray = new int[4]; - Vector3f bindPosition = new Vector3f(); - Map coordsMap = new HashMap<>(32); - for (Mesh mesh : meshes) { - int numVertices = mesh.getVertexCount(); - for (int vertexI = 0; vertexI < numVertices; ++vertexI) { - String managerName = findManager( - mesh, vertexI, iArray, wArray, managerMap); - VectorSet set = coordsMap.get(managerName); - if (set == null) { - set = new VectorSetUsingBuffer(1, false); - coordsMap.put(managerName, set); - } - MyMesh.vertexVector3f(mesh, VertexBuffer.Type.BindPosePosition, - vertexI, bindPosition); - set.add(bindPosition); - } - } - - return coordsMap; - } - - /** - * Find the main root bone of a Skeleton, based on its total mesh weight. - * - * @param skeleton the skeleton (not null, unaffected) - * @param targetMeshes an array of animated meshes to provide bone weights - * (not null) - * @return a root bone, or null if none found - */ - public static Bone findMainBone(Skeleton skeleton, Mesh[] targetMeshes) { - Validate.nonNull(targetMeshes, "target meshes"); - - Bone[] rootBones = skeleton.getRoots(); - - Bone result; - if (rootBones.length == 1) { - result = rootBones[0]; - } else { - result = null; - float[] totalWeights = totalWeights(targetMeshes, skeleton); - float greatestTotalWeight = Float.NEGATIVE_INFINITY; - for (Bone rootBone : rootBones) { - int boneIndex = skeleton.getBoneIndex(rootBone); - float weight = totalWeights[boneIndex]; - if (weight > greatestTotalWeight) { - result = rootBone; - greatestTotalWeight = weight; - } - } - } - - return result; - } - - /** - * Find the main root joint of an Armature, based on its total mesh weight. - * - * @param armature the Armature (not null, unaffected) - * @param targetMeshes an array of animated meshes to provide weights (not - * null) - * @return a root joint, or null if none found - */ - public static Joint findMainJoint(Armature armature, Mesh[] targetMeshes) { - Validate.nonNull(targetMeshes, "target meshes"); - - Joint[] roots = armature.getRoots(); - - Joint result; - if (roots.length == 1) { - result = roots[0]; - } else { - result = null; - float[] totalWeights = totalWeights(targetMeshes, armature); - float greatestTotalWeight = Float.NEGATIVE_INFINITY; - for (Joint root : roots) { - int jointIndex = root.getId(); - float weight = totalWeights[jointIndex]; - if (weight > greatestTotalWeight) { - result = root; - greatestTotalWeight = weight; - } - } - } - - return result; - } - - /** - * Determine which physics link should manage the specified mesh vertex. - * - * @param mesh the mesh containing the vertex (not null, unaffected) - * @param vertexIndex the vertex index in the mesh (≥0) - * @param iArray temporary storage for bone indices (not null, modified) - * @param wArray temporary storage for bone weights (not null, modified) - * @param managerMap a map from bone indices to bone/torso names (not null, - * unaffected) - * @return a bone/torso name - */ - public static String findManager(Mesh mesh, int vertexIndex, int[] iArray, - float[] wArray, String[] managerMap) { - Validate.nonNull(mesh, "mesh"); - Validate.nonNegative(vertexIndex, "vertex index"); - Validate.nonNull(iArray, "index array"); - Validate.nonNull(wArray, "weight array"); - Validate.nonNull(managerMap, "manager map"); - - MyMesh.vertexBoneIndices(mesh, vertexIndex, iArray); - MyMesh.vertexBoneWeights(mesh, vertexIndex, wArray); - Map weightMap = weightMap(iArray, wArray, managerMap); - - float bestTotalWeight = Float.NEGATIVE_INFINITY; - String bestName = null; - for (Map.Entry entry : weightMap.entrySet()) { - float totalWeight = entry.getValue(); - if (totalWeight >= bestTotalWeight) { - bestTotalWeight = totalWeight; - bestName = entry.getKey(); - } - } - - return bestName; - } - - /** - * Access the SkeletonControl or SkinningControl in the specified subtree, - * assuming it doesn't contain more than one. - * - * @param subtree a subtree of a scene graph (may be null, unaffected) - * @return the pre-existing instance, or null if none or multiple - */ - public static AbstractControl findSControl(Spatial subtree) { - AbstractControl result = null; - if (subtree != null) { - List skinners = MySpatial.listControls( - subtree, SkinningControl.class, null); - List skellers = MySpatial.listControls( - subtree, SkeletonControl.class, null); - if (skellers.isEmpty() && skinners.size() == 1) { - result = skinners.get(0); - } else if (skinners.isEmpty() && skellers.size() == 1) { - result = skellers.get(0); - } - } - - return result; - } - - /** - * Traverse a group of bodies joined by physics joints, adding bodies to the - * ignore list of the start body. Note: recursive! - * - * @param start the body where the traversal began (not null, unaffected) - * @param current the body to traverse (not null, unaffected) - * @param hopsRemaining the number of hops remaining (≥0) - * @param visited map bodies visited during the current traversal to max - * remaining hops (not null, modified) - */ - static void ignoreCollisions(PhysicsBody start, PhysicsBody current, - int hopsRemaining, Map visited) { - if (hopsRemaining <= 0) { - return; - } - int newRemainingHops = hopsRemaining - 1; - - // Consider each neighboring body that isn't the starting body. - PhysicsJoint[] joints = current.listJoints(); - for (PhysicsJoint joint : joints) { - PhysicsBody neighbor = joint.findOtherBody(current); - if (neighbor != null && neighbor != start) { - // Decide whether to visit (or re-visit) the neighbor. - boolean visit = true; - if (visited.containsKey(neighbor)) { // previously visited - int mostRemainingHops = visited.get(neighbor); - if (newRemainingHops <= mostRemainingHops) { - // don't revisit - visit = false; - } - } - if (visit) { - start.addToIgnoreList(neighbor); - visited.put(neighbor, newRemainingHops); - ignoreCollisions( - start, neighbor, newRemainingHops, visited); - } - } - } - } - - /** - * Enumerate all animated meshes in the specified subtree of a scene graph, - * skipping spatials tagged with "JmePhysicsIgnore". Note: recursive! - * - * @param subtree the subtree to analyze (may be null, aliases created) - * @param storeResult storage for results (added to if not null) - * @return an expanded List (either storeResult or a new List) - */ - public static List - listDacMeshes(Spatial subtree, List storeResult) { - List result = (storeResult == null) - ? new ArrayList(10) : storeResult; - - if (subtree != null) { - Boolean ignore = subtree.getUserData(UserData.JME_PHYSICSIGNORE); - if (ignore != null && ignore) { - return result; - } - } - - if (subtree instanceof Geometry) { - Geometry geometry = (Geometry) subtree; - Mesh mesh = geometry.getMesh(); - if (MyMesh.isAnimated(mesh) && !result.contains(mesh)) { - result.add(mesh); - } - - } else if (subtree instanceof Node) { - Node node = (Node) subtree; - List children = node.getChildren(); - for (Spatial child : children) { - listDacMeshes(child, result); - } - } - - return result; - } - - /** - * Enumerate all physics joints that connect bodies in the specified array. - * - * @param bodies the array to search (not null, unaffected) - * @return a new Set of pre-existing instances - */ - public static Set listInternalJoints(PhysicsBody... bodies) { - Set result = new TreeSet<>(); - - for (PhysicsBody body : bodies) { - PhysicsJoint[] joints = body.listJoints(); - for (PhysicsJoint joint : joints) { - PhysicsBody otherBody = joint.findOtherBody(body); - - // Is otherBody found in the array? - for (Object b : bodies) { - if (b == otherBody) { - result.add(joint); - break; - } - } - } - } - - return result; - } - - /** - * Create a transformed CylinderCollisionShape that bounds the locations in - * the specified VectorSet. - * - * @param vectorSet the set of locations (not null, numVectors>1, - * unaffected) - * @param scaleFactors scale factors to apply to local coordinates (not - * null, unaffected) - * @return a new shape - */ - public static CompoundCollisionShape - makeCylinder(VectorSet vectorSet, Vector3f scaleFactors) { - Validate.nonNull(scaleFactors, "scale factors"); - int numVectors = vectorSet.numVectors(); - Validate.require(numVectors > 1, "multiple vectors"); - - RectangularSolid solid = makeRectangularSolid(vectorSet, scaleFactors); - - Vector3f halfExtents = solid.halfExtents(null); // in local coordinates - float max = MyMath.max(halfExtents.x, halfExtents.y, halfExtents.z); - float mid = MyMath.mid(halfExtents.x, halfExtents.y, halfExtents.z); - float min = MyMath.min(halfExtents.x, halfExtents.y, halfExtents.z); - float halfHeight; - if (max - mid > mid - min) { // prolate - halfHeight = max; - } else { // oblate - halfHeight = min; - } - Vector3f heightDirection = new Vector3f(); // in local coordinates - if (halfHeight == halfExtents.x) { - heightDirection.set(1f, 0f, 0f); - } else if (halfHeight == halfExtents.y) { - heightDirection.set(0f, 1f, 0f); - } else { - assert halfHeight == halfExtents.z; - heightDirection.set(0f, 0f, 1f); - } - - // Convert heightDirection to world coordinates: - Quaternion localToWorld = solid.localToWorld(null); - MyQuaternion.rotate(localToWorld, heightDirection, heightDirection); - - // Calculate minimum half height and squared radius for the cylinder. - halfHeight = 0f; - double squaredRadius = 0.; - FloatBuffer buffer = vectorSet.toBuffer(); - Vector3f worldCenter = vectorSet.mean(null); - Vector3f tempVector = new Vector3f(); - buffer.rewind(); - while (buffer.hasRemaining()) { - tempVector.x = buffer.get(); - tempVector.y = buffer.get(); - tempVector.z = buffer.get(); - tempVector.subtractLocal(worldCenter); - - // update halfHeight - float h = tempVector.dot(heightDirection); - float absH = FastMath.abs(h); - if (absH > halfHeight) { - halfHeight = absH; - } - - // update squaredRadius - MyVector3f.accumulateScaled(tempVector, heightDirection, -h); - double r2 = MyVector3f.lengthSquared(tempVector); - if (r2 > squaredRadius) { - squaredRadius = r2; - } - } - - // Generate the cylinder shape. - float height = 2f * halfHeight; - float radius = (float) Math.sqrt(squaredRadius); - CylinderCollisionShape cylinder - = new CylinderCollisionShape(radius, height, MyVector3f.xAxis); - - // Choose an orientation for the cylinder. - Vector3f yAxis = new Vector3f(); - Vector3f zAxis = new Vector3f(); - MyVector3f.generateBasis(heightDirection, yAxis, zAxis); - Matrix3f cylinderOrientation = new Matrix3f(); - cylinderOrientation.fromAxes(heightDirection, yAxis, zAxis); - - CompoundCollisionShape result = new CompoundCollisionShape(); - result.addChildShape(cylinder, worldCenter, cylinderOrientation); - - return result; - } - - /** - * Instantiate a compact RectangularSolid that bounds the sample locations - * contained in a VectorSet. - * - * @param vectorSet the set of locations (not null, numVectors>1, - * contents unaffected) - * @param scaleFactors scale factors to apply to local coordinates (not - * null, unaffected) - * @return a new RectangularSolid - */ - public static RectangularSolid - makeRectangularSolid(VectorSet vectorSet, Vector3f scaleFactors) { - Validate.nonNull(scaleFactors, "scale factors"); - int numVectors = vectorSet.numVectors(); - Validate.require(numVectors > 1, "multiple vectors"); - - // Orient local axes based on the eigenvectors of the covariance matrix. - Matrix3f covariance = vectorSet.covariance(null); - Eigen3f eigen = new Eigen3f(covariance); - Vector3f[] basis = eigen.getEigenVectors(); - Quaternion localToWorld = new Quaternion().fromAxes(basis); - - // Calculate the min and max for each local axis. - Vector3f maxima = new Vector3f(Float.NEGATIVE_INFINITY, - Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - Vector3f minima = new Vector3f(Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - Vector3f tempVector = new Vector3f(); - FloatBuffer buffer = vectorSet.toBuffer(); - buffer.rewind(); - while (buffer.hasRemaining()) { - tempVector.x = buffer.get(); - tempVector.y = buffer.get(); - tempVector.z = buffer.get(); - MyQuaternion.rotateInverse(localToWorld, tempVector, tempVector); - MyVector3f.accumulateMaxima(maxima, tempVector); - MyVector3f.accumulateMinima(minima, tempVector); - } - - // Apply scale factors to local coordinates of extrema. - Vector3f center = MyVector3f.midpoint(minima, maxima, null); - - maxima.subtractLocal(center); - maxima.multLocal(scaleFactors); - maxima.addLocal(center); - - minima.subtractLocal(center); - minima.multLocal(scaleFactors); - minima.addLocal(center); - - RectangularSolid result - = new RectangularSolid(minima, maxima, localToWorld); - - return result; - } - - /** - * Convert a transform from the mesh coordinate system to the local - * coordinate system of the specified bone. - * - * @param parentBone (not null) - * @param transform the transform to convert (not null, modified) - */ - public static void meshToLocal(Bone parentBone, Transform transform) { - Quaternion msr = parentBone.getModelSpaceRotation(); - Validate.require(msr.norm() > 0f, "non-zero parent rotation"); - - Vector3f location = transform.getTranslation(); // alias - Quaternion orientation = transform.getRotation(); // alias - Vector3f scale = transform.getScale(); // alias - - Vector3f pmTranslate = parentBone.getModelSpacePosition(); - Vector3f pmScale = parentBone.getModelSpaceScale(); - - location.subtractLocal(pmTranslate); - location.divideLocal(pmScale); - MyQuaternion.rotateInverse(msr, location, location); - scale.divideLocal(pmScale); - msr.inverse().mult(orientation, orientation); - } - - /** - * Convert a Transform from the mesh coordinate system to the local - * coordinate system of the specified armature joint. - * - * @param parent (not null) - * @param transform the transform to convert (not null, modified) - */ - static void meshToLocal(Joint parent, Transform transform) { - Transform pm = parent.getModelTransform(); - Validate.require( - pm.getRotation().norm() > 0f, "non-zero parent rotation"); - - Vector3f location = transform.getTranslation(); // alias - Quaternion orientation = transform.getRotation(); // alias - Vector3f scale = transform.getScale(); // alias - - Vector3f pmTranslate = pm.getTranslation(); // alias - Quaternion pmRotate = pm.getRotation(); // alias - Vector3f pmScale = pm.getScale(); // alias - - location.subtractLocal(pmTranslate); - location.divideLocal(pmScale); - MyQuaternion.rotateInverse(pmRotate, location, location); - scale.divideLocal(pmScale); - pmRotate.inverse().mult(orientation, orientation); - } - - /** - * Read an array of transforms from an input capsule. - * - * @param capsule the input capsule (not null) - * @param fieldName the name of the field to read (not null) - * @return a new array or null - * @throws IOException from capsule - */ - public static Transform[] readTransformArray( - InputCapsule capsule, String fieldName) throws IOException { - Validate.nonNull(capsule, "capsule"); - Validate.nonNull(fieldName, "field name"); - - Savable[] tmp = capsule.readSavableArray(fieldName, null); - Transform[] result; - if (tmp == null) { - result = null; - } else { - result = new Transform[tmp.length]; - for (int i = 0; i < tmp.length; ++i) { - result[i] = (Transform) tmp[i]; - } - } - - return result; - } - - /** - * Calculate a coordinate transform for the specified Spatial relative to a - * specified ancestor node. The result incorporates the transform of the - * starting Spatial, but not that of the ancestor. - * - * @param startSpatial the starting Spatial (not null, unaffected) - * @param ancestorNode the ancestor node (not null, unaffected) - * @param storeResult storage for the result (modified if not null) - * @return a coordinate transform (either storeResult or a new vector, not - * null) - */ - public static Transform relativeTransform( - Spatial startSpatial, Node ancestorNode, Transform storeResult) { - Validate.nonNull(startSpatial, "spatial"); - Validate.nonNull(ancestorNode, "ancestor"); - assert startSpatial.hasAncestor(ancestorNode); - Transform result - = (storeResult == null) ? new Transform() : storeResult; - - result.loadIdentity(); - Spatial loopSpatial = startSpatial; - while (loopSpatial != ancestorNode) { - Transform localTransform = loopSpatial.getLocalTransform(); // alias - MyMath.combine(result, localTransform, result); - loopSpatial = loopSpatial.getParent(); - } - - return result; - } - - /** - * Validate an Armature for use with DynamicAnimControl. - * - * @param armature the Armature to validate (not null, unaffected) - */ - public static void validate(Armature armature) { - int numJoints = armature.getJointCount(); - if (numJoints < 0) { - throw new IllegalArgumentException("Joint count is negative!"); - } - - Collection nameSet = new TreeSet<>(); - for (int jointIndex = 0; jointIndex < numJoints; ++jointIndex) { - Joint joint = armature.getJoint(jointIndex); - if (joint == null) { - String msg = String.format( - "Joint %d in armature is null!", jointIndex); - throw new IllegalArgumentException(msg); - } - String jointName = joint.getName(); - if (jointName == null) { - String message = String.format( - "Joint %d in armature has null name!", jointIndex); - throw new IllegalArgumentException(message); - } else if (jointName.equals(DacConfiguration.torsoName)) { - String message = String.format( - "Joint %d in armature has a reserved name!", - jointIndex); - throw new IllegalArgumentException(message); - } else if (nameSet.contains(jointName)) { - String message - = "Duplicate joint name in skeleton: " + jointName; - throw new IllegalArgumentException(message); - } - nameSet.add(jointName); - } - } - - /** - * Validate a Skeleton for use with DynamicAnimControl. - * - * @param skeleton the Skeleton to validate (not null, unaffected) - */ - public static void validate(Skeleton skeleton) { - int numBones = skeleton.getBoneCount(); - if (numBones < 0) { - throw new IllegalArgumentException("Bone count is negative!"); - } - - Collection nameSet = new TreeSet<>(); - for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { - Bone bone = skeleton.getBone(boneIndex); - if (bone == null) { - String msg = String.format( - "Bone %d in skeleton is null!", boneIndex); - throw new IllegalArgumentException(msg); - } - String boneName = bone.getName(); - if (boneName == null) { - String msg = String.format( - "Bone %d in skeleton has null name!", boneIndex); - throw new IllegalArgumentException(msg); - } else if (boneName.equals(DacConfiguration.torsoName)) { - String msg = String.format( - "Bone %d in skeleton has a reserved name!", boneIndex); - throw new IllegalArgumentException(msg); - } else if (nameSet.contains(boneName)) { - String msg = "Duplicate bone name in skeleton: " - + MyString.quote(boneName); - throw new IllegalArgumentException(msg); - } - nameSet.add(boneName); - } - } - - /** - * Validate a model for use with DynamicAnimControl. - * - * @param model the model to validate (not null, unaffected) - * @throws IllegalArgumentException for an invalid model - */ - public static void validate(Spatial model) { - Validate.nonNull(model, "model"); - - List geometries = MySpatial.listGeometries(model); - if (geometries.isEmpty()) { - throw new IllegalArgumentException("No meshes in the model."); - } - for (Geometry geometry : geometries) { - if (geometry.isIgnoreTransform()) { - throw new IllegalArgumentException( - "A model geometry ignores transforms."); - } - } - } - // ************************************************************************* - // private methods - - /** - * Add the vertex weights of each bone in the specified mesh to an array of - * total weights. - * - * @param mesh animated mesh to analyze (not null, unaffected) - * @param totalWeights (not null, modified) - */ - private static void addWeights(Mesh mesh, float[] totalWeights) { - assert totalWeights != null; - - int maxWeightsPerVert = mesh.getMaxNumWeights(); - if (maxWeightsPerVert <= 0) { - maxWeightsPerVert = 1; - } - assert maxWeightsPerVert > 0 : maxWeightsPerVert; - assert maxWeightsPerVert <= 4 : maxWeightsPerVert; - - VertexBuffer biBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex); - Buffer boneIndexBuffer = biBuf.getDataReadOnly(); - boneIndexBuffer.rewind(); - int numBoneIndices = boneIndexBuffer.remaining(); - assert numBoneIndices % 4 == 0 : numBoneIndices; - int numVertices = boneIndexBuffer.remaining() / 4; - - FloatBuffer weightBuffer - = mesh.getFloatBuffer(VertexBuffer.Type.BoneWeight); - weightBuffer.rewind(); - int numWeights = weightBuffer.remaining(); - assert numWeights == numVertices * 4 : numWeights; - - for (int vIndex = 0; vIndex < numVertices; ++vIndex) { - for (int wIndex = 0; wIndex < 4; ++wIndex) { - float weight = weightBuffer.get(); - int boneIndex = MyBuffer.readIndex(boneIndexBuffer); - if (wIndex < maxWeightsPerVert) { - totalWeights[boneIndex] += FastMath.abs(weight); - } - } - } - } - - /** - * Calculate the total mesh weight animated by each Joint in the specified - * meshes. - * - * @param meshes the animated meshes to analyze (not null, unaffected) - * @param armature (not null, unaffected) - * @return a map from joint indices to total mesh weight - */ - private static float[] totalWeights(Mesh[] meshes, Armature armature) { - Validate.nonNull(meshes, "meshes"); - - int numBones = armature.getJointCount(); - float[] result = new float[numBones]; - for (Mesh mesh : meshes) { - addWeights(mesh, result); - } - - List joints = MySkeleton.preOrderJoints(armature); - Collections.reverse(joints); - for (Joint childJoint : joints) { - int childIndex = childJoint.getId(); - Joint parent = childJoint.getParent(); - if (parent != null) { - int parentIndex = parent.getId(); - result[parentIndex] += result[childIndex]; - } - } - - return result; - } - - /** - * Calculate the total mesh weight animated by each Bone in the specified - * meshes. - * - * @param meshes the animated meshes to analyze (not null, unaffected) - * @param skeleton (not null, unaffected) - * @return a map from bone indices to total mesh weight - */ - private static float[] totalWeights(Mesh[] meshes, Skeleton skeleton) { - Validate.nonNull(meshes, "meshes"); - - int numBones = skeleton.getBoneCount(); - float[] result = new float[numBones]; - for (Mesh mesh : meshes) { - addWeights(mesh, result); - } - - List bones = MySkeleton.preOrderBones(skeleton); - Collections.reverse(bones); - for (Bone childBone : bones) { - int childIndex = skeleton.getBoneIndex(childBone); - Bone parent = childBone.getParent(); - if (parent != null) { - int parentIndex = skeleton.getBoneIndex(parent); - result[parentIndex] += result[childIndex]; - } - } - - return result; - } - - /** - * Tabulate the total bone weight associated with each bone/torso link in a - * ragdoll. - * - * @param biArray the array of bone indices (not null, unaffected) - * @param bwArray the array of bone weights (not null, unaffected) - * @param managerMap a map from bone indices to managing link names (not - * null, unaffected) - * @return a new map from link names to total weight - */ - private static Map - weightMap(int[] biArray, float[] bwArray, String[] managerMap) { - assert biArray.length == 4; - assert bwArray.length == 4; - - Map weightMap = new HashMap<>(4); - for (int j = 0; j < 4; ++j) { - int boneIndex = biArray[j]; - if (boneIndex != -1) { - String managerName = managerMap[boneIndex]; - if (weightMap.containsKey(managerName)) { - float oldWeight = weightMap.get(managerName); - float newWeight = oldWeight + bwArray[j]; - weightMap.put(managerName, newWeight); - } else { - weightMap.put(managerName, bwArray[j]); - } - } - } - - return weightMap; - } -} +/* + * Copyright (c) 2018-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.animation; + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SkeletonControl; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.export.InputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.Eigen3f; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.UserData; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.control.AbstractControl; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Logger; +import jme3utilities.MyMesh; +import jme3utilities.MySkeleton; +import jme3utilities.MySpatial; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyQuaternion; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.RectangularSolid; +import jme3utilities.math.VectorSet; +import jme3utilities.math.VectorSetUsingBuffer; + +/** + * Utility methods used by DynamicAnimControl and associated classes. + * + * @author Stephen Gold sgold@sonic.net + * + * Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). + */ +final public class RagUtils { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(RagUtils.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private RagUtils() { + } + // ************************************************************************* + // new methods exposed + + /** + * Assign each mesh vertex to a bone/torso link and add its location (mesh + * coordinates in bind pose) to that link's list. + *

+ * A software skin update must precede any request for vertex locations. + * TODO use the Wes library to avoid this limitation? + * + * @param meshes array of animated meshes to use (not null, unaffected) + * @param managerMap a map from bone indices to managing link names (not + * null, unaffected) + * @return a new map from bone/torso names to sets of vertex coordinates + */ + public static Map + coordsMap(Mesh[] meshes, String[] managerMap) { + Validate.nonNull(managerMap, "manager map"); + + float[] wArray = new float[4]; + int[] iArray = new int[4]; + Vector3f bindPosition = new Vector3f(); + Map coordsMap = new HashMap<>(32); + for (Mesh mesh : meshes) { + int numVertices = mesh.getVertexCount(); + for (int vertexI = 0; vertexI < numVertices; ++vertexI) { + String managerName = findManager( + mesh, vertexI, iArray, wArray, managerMap); + VectorSet set = coordsMap.get(managerName); + if (set == null) { + set = new VectorSetUsingBuffer(1, false); + coordsMap.put(managerName, set); + } + MyMesh.vertexVector3f(mesh, VertexBuffer.Type.BindPosePosition, + vertexI, bindPosition); + set.add(bindPosition); + } + } + + return coordsMap; + } + + /** + * Find the main root bone of a Skeleton, based on its total mesh weight. + * + * @param skeleton the skeleton (not null, unaffected) + * @param targetMeshes an array of animated meshes to provide bone weights + * (not null) + * @return a root bone, or null if none found + */ + public static Bone findMainBone(Skeleton skeleton, Mesh[] targetMeshes) { + Validate.nonNull(targetMeshes, "target meshes"); + + Bone[] rootBones = skeleton.getRoots(); + + Bone result; + if (rootBones.length == 1) { + result = rootBones[0]; + } else { + result = null; + float[] totalWeights = totalWeights(targetMeshes, skeleton); + float greatestTotalWeight = Float.NEGATIVE_INFINITY; + for (Bone rootBone : rootBones) { + int boneIndex = skeleton.getBoneIndex(rootBone); + float weight = totalWeights[boneIndex]; + if (weight > greatestTotalWeight) { + result = rootBone; + greatestTotalWeight = weight; + } + } + } + + return result; + } + + /** + * Find the main root joint of an Armature, based on its total mesh weight. + * + * @param armature the Armature (not null, unaffected) + * @param targetMeshes an array of animated meshes to provide weights (not + * null) + * @return a root joint, or null if none found + */ + public static Joint findMainJoint(Armature armature, Mesh[] targetMeshes) { + Validate.nonNull(targetMeshes, "target meshes"); + + Joint[] roots = armature.getRoots(); + + Joint result; + if (roots.length == 1) { + result = roots[0]; + } else { + result = null; + float[] totalWeights = totalWeights(targetMeshes, armature); + float greatestTotalWeight = Float.NEGATIVE_INFINITY; + for (Joint root : roots) { + int jointIndex = root.getId(); + float weight = totalWeights[jointIndex]; + if (weight > greatestTotalWeight) { + result = root; + greatestTotalWeight = weight; + } + } + } + + return result; + } + + /** + * Determine which physics link should manage the specified mesh vertex. + * + * @param mesh the mesh containing the vertex (not null, unaffected) + * @param vertexIndex the vertex index in the mesh (≥0) + * @param iArray temporary storage for bone indices (not null, modified) + * @param wArray temporary storage for bone weights (not null, modified) + * @param managerMap a map from bone indices to bone/torso names (not null, + * unaffected) + * @return a bone/torso name + */ + public static String findManager(Mesh mesh, int vertexIndex, int[] iArray, + float[] wArray, String[] managerMap) { + Validate.nonNull(mesh, "mesh"); + Validate.nonNegative(vertexIndex, "vertex index"); + Validate.nonNull(iArray, "index array"); + Validate.nonNull(wArray, "weight array"); + Validate.nonNull(managerMap, "manager map"); + + MyMesh.vertexBoneIndices(mesh, vertexIndex, iArray); + MyMesh.vertexBoneWeights(mesh, vertexIndex, wArray); + Map weightMap = weightMap(iArray, wArray, managerMap); + + float bestTotalWeight = Float.NEGATIVE_INFINITY; + String bestName = null; + for (Map.Entry entry : weightMap.entrySet()) { + float totalWeight = entry.getValue(); + if (totalWeight >= bestTotalWeight) { + bestTotalWeight = totalWeight; + bestName = entry.getKey(); + } + } + + return bestName; + } + + /** + * Access the SkeletonControl or SkinningControl in the specified subtree, + * assuming it doesn't contain more than one. + * + * @param subtree a subtree of a scene graph (may be null, unaffected) + * @return the pre-existing instance, or null if none or multiple + */ + public static AbstractControl findSControl(Spatial subtree) { + AbstractControl result = null; + if (subtree != null) { + List skinners = MySpatial.listControls( + subtree, SkinningControl.class, null); + List skellers = MySpatial.listControls( + subtree, SkeletonControl.class, null); + if (skellers.isEmpty() && skinners.size() == 1) { + result = skinners.get(0); + } else if (skinners.isEmpty() && skellers.size() == 1) { + result = skellers.get(0); + } + } + + return result; + } + + /** + * Traverse a group of bodies joined by physics joints, adding bodies to the + * ignore list of the start body. Note: recursive! + * + * @param start the body where the traversal began (not null, unaffected) + * @param current the body to traverse (not null, unaffected) + * @param hopsRemaining the number of hops remaining (≥0) + * @param visited map bodies visited during the current traversal to max + * remaining hops (not null, modified) + */ + static void ignoreCollisions(PhysicsBody start, PhysicsBody current, + int hopsRemaining, Map visited) { + if (hopsRemaining <= 0) { + return; + } + int newRemainingHops = hopsRemaining - 1; + + // Consider each neighboring body that isn't the starting body. + PhysicsJoint[] joints = current.listJoints(); + for (PhysicsJoint joint : joints) { + PhysicsBody neighbor = joint.findOtherBody(current); + if (neighbor != null && neighbor != start) { + // Decide whether to visit (or re-visit) the neighbor. + boolean visit = true; + if (visited.containsKey(neighbor)) { // previously visited + int mostRemainingHops = visited.get(neighbor); + if (newRemainingHops <= mostRemainingHops) { + // don't revisit + visit = false; + } + } + if (visit) { + start.addToIgnoreList(neighbor); + visited.put(neighbor, newRemainingHops); + ignoreCollisions( + start, neighbor, newRemainingHops, visited); + } + } + } + } + + /** + * Enumerate all animated meshes in the specified subtree of a scene graph, + * skipping spatials tagged with "JmePhysicsIgnore". Note: recursive! + * + * @param subtree the subtree to analyze (may be null, aliases created) + * @param storeResult storage for results (added to if not null) + * @return an expanded List (either storeResult or a new List) + */ + public static List + listDacMeshes(Spatial subtree, List storeResult) { + List result = (storeResult == null) + ? new ArrayList(10) : storeResult; + + if (subtree != null) { + Boolean ignore = subtree.getUserData(UserData.JME_PHYSICSIGNORE); + if (ignore != null && ignore) { + return result; + } + } + + if (subtree instanceof Geometry) { + Geometry geometry = (Geometry) subtree; + Mesh mesh = geometry.getMesh(); + if (MyMesh.isAnimated(mesh) && !result.contains(mesh)) { + result.add(mesh); + } + + } else if (subtree instanceof Node) { + Node node = (Node) subtree; + List children = node.getChildren(); + for (Spatial child : children) { + listDacMeshes(child, result); + } + } + + return result; + } + + /** + * Enumerate all physics joints that connect bodies in the specified array. + * + * @param bodies the array to search (not null, unaffected) + * @return a new Set of pre-existing instances + */ + public static Set listInternalJoints(PhysicsBody... bodies) { + Set result = new TreeSet<>(); + + for (PhysicsBody body : bodies) { + PhysicsJoint[] joints = body.listJoints(); + for (PhysicsJoint joint : joints) { + PhysicsBody otherBody = joint.findOtherBody(body); + + // Is otherBody found in the array? + for (Object b : bodies) { + if (b == otherBody) { + result.add(joint); + break; + } + } + } + } + + return result; + } + + /** + * Create a transformed CylinderCollisionShape that bounds the locations in + * the specified VectorSet. + * + * @param vectorSet the set of locations (not null, numVectors>1, + * unaffected) + * @param scaleFactors scale factors to apply to local coordinates (not + * null, unaffected) + * @return a new shape + */ + public static CompoundCollisionShape + makeCylinder(VectorSet vectorSet, Vector3f scaleFactors) { + Validate.nonNull(scaleFactors, "scale factors"); + int numVectors = vectorSet.numVectors(); + Validate.require(numVectors > 1, "multiple vectors"); + + RectangularSolid solid = makeRectangularSolid(vectorSet, scaleFactors); + + Vector3f halfExtents = solid.halfExtents(null); // in local coordinates + float max = MyMath.max(halfExtents.x, halfExtents.y, halfExtents.z); + float mid = MyMath.mid(halfExtents.x, halfExtents.y, halfExtents.z); + float min = MyMath.min(halfExtents.x, halfExtents.y, halfExtents.z); + float halfHeight; + if (max - mid > mid - min) { // prolate + halfHeight = max; + } else { // oblate + halfHeight = min; + } + Vector3f heightDirection = new Vector3f(); // in local coordinates + if (halfHeight == halfExtents.x) { + heightDirection.set(1f, 0f, 0f); + } else if (halfHeight == halfExtents.y) { + heightDirection.set(0f, 1f, 0f); + } else { + assert halfHeight == halfExtents.z; + heightDirection.set(0f, 0f, 1f); + } + + // Convert heightDirection to world coordinates: + Quaternion localToWorld = solid.localToWorld(null); + MyQuaternion.rotate(localToWorld, heightDirection, heightDirection); + + // Calculate minimum half height and squared radius for the cylinder. + halfHeight = 0f; + double squaredRadius = 0.; + FloatBuffer buffer = vectorSet.toBuffer(); + Vector3f worldCenter = vectorSet.mean(null); + Vector3f tempVector = new Vector3f(); + buffer.rewind(); + while (buffer.hasRemaining()) { + tempVector.x = buffer.get(); + tempVector.y = buffer.get(); + tempVector.z = buffer.get(); + tempVector.subtractLocal(worldCenter); + + // update halfHeight + float h = tempVector.dot(heightDirection); + float absH = FastMath.abs(h); + if (absH > halfHeight) { + halfHeight = absH; + } + + // update squaredRadius + MyVector3f.accumulateScaled(tempVector, heightDirection, -h); + double r2 = MyVector3f.lengthSquared(tempVector); + if (r2 > squaredRadius) { + squaredRadius = r2; + } + } + + // Generate the cylinder shape. + float height = 2f * halfHeight; + float radius = (float) Math.sqrt(squaredRadius); + CylinderCollisionShape cylinder + = new CylinderCollisionShape(radius, height, MyVector3f.xAxis); + + // Choose an orientation for the cylinder. + Vector3f yAxis = new Vector3f(); + Vector3f zAxis = new Vector3f(); + MyVector3f.generateBasis(heightDirection, yAxis, zAxis); + Matrix3f cylinderOrientation = new Matrix3f(); + cylinderOrientation.fromAxes(heightDirection, yAxis, zAxis); + + CompoundCollisionShape result = new CompoundCollisionShape(); + result.addChildShape(cylinder, worldCenter, cylinderOrientation); + + return result; + } + + /** + * Instantiate a compact RectangularSolid that bounds the sample locations + * contained in a VectorSet. + * + * @param vectorSet the set of locations (not null, numVectors>1, + * contents unaffected) + * @param scaleFactors scale factors to apply to local coordinates (not + * null, unaffected) + * @return a new RectangularSolid + */ + public static RectangularSolid + makeRectangularSolid(VectorSet vectorSet, Vector3f scaleFactors) { + Validate.nonNull(scaleFactors, "scale factors"); + int numVectors = vectorSet.numVectors(); + Validate.require(numVectors > 1, "multiple vectors"); + + // Orient local axes based on the eigenvectors of the covariance matrix. + Matrix3f covariance = vectorSet.covariance(null); + Eigen3f eigen = new Eigen3f(covariance); + Vector3f[] basis = eigen.getEigenVectors(); + Quaternion localToWorld = new Quaternion().fromAxes(basis); + + // Calculate the min and max for each local axis. + Vector3f maxima = new Vector3f(Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + Vector3f minima = new Vector3f(Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + Vector3f tempVector = new Vector3f(); + FloatBuffer buffer = vectorSet.toBuffer(); + buffer.rewind(); + while (buffer.hasRemaining()) { + tempVector.x = buffer.get(); + tempVector.y = buffer.get(); + tempVector.z = buffer.get(); + MyQuaternion.rotateInverse(localToWorld, tempVector, tempVector); + MyVector3f.accumulateMaxima(maxima, tempVector); + MyVector3f.accumulateMinima(minima, tempVector); + } + + // Apply scale factors to local coordinates of extrema. + Vector3f center = MyVector3f.midpoint(minima, maxima, null); + + maxima.subtractLocal(center); + maxima.multLocal(scaleFactors); + maxima.addLocal(center); + + minima.subtractLocal(center); + minima.multLocal(scaleFactors); + minima.addLocal(center); + + RectangularSolid result + = new RectangularSolid(minima, maxima, localToWorld); + + return result; + } + + /** + * Convert a transform from the mesh coordinate system to the local + * coordinate system of the specified bone. + * + * @param parentBone (not null) + * @param transform the transform to convert (not null, modified) + */ + public static void meshToLocal(Bone parentBone, Transform transform) { + Quaternion msr = parentBone.getModelSpaceRotation(); + Validate.require(msr.norm() > 0f, "non-zero parent rotation"); + + Vector3f location = transform.getTranslation(); // alias + Quaternion orientation = transform.getRotation(); // alias + Vector3f scale = transform.getScale(); // alias + + Vector3f pmTranslate = parentBone.getModelSpacePosition(); + Vector3f pmScale = parentBone.getModelSpaceScale(); + + location.subtractLocal(pmTranslate); + location.divideLocal(pmScale); + MyQuaternion.rotateInverse(msr, location, location); + scale.divideLocal(pmScale); + msr.inverse().mult(orientation, orientation); + } + + /** + * Convert a Transform from the mesh coordinate system to the local + * coordinate system of the specified armature joint. + * + * @param parent (not null) + * @param transform the transform to convert (not null, modified) + */ + static void meshToLocal(Joint parent, Transform transform) { + Transform pm = parent.getModelTransform(); + Validate.require( + pm.getRotation().norm() > 0f, "non-zero parent rotation"); + + Vector3f location = transform.getTranslation(); // alias + Quaternion orientation = transform.getRotation(); // alias + Vector3f scale = transform.getScale(); // alias + + Vector3f pmTranslate = pm.getTranslation(); // alias + Quaternion pmRotate = pm.getRotation(); // alias + Vector3f pmScale = pm.getScale(); // alias + + location.subtractLocal(pmTranslate); + location.divideLocal(pmScale); + MyQuaternion.rotateInverse(pmRotate, location, location); + scale.divideLocal(pmScale); + pmRotate.inverse().mult(orientation, orientation); + } + + /** + * Read an array of transforms from an input capsule. + * + * @param capsule the input capsule (not null) + * @param fieldName the name of the field to read (not null) + * @return a new array or null + * @throws IOException from capsule + */ + public static Transform[] readTransformArray( + InputCapsule capsule, String fieldName) throws IOException { + Validate.nonNull(capsule, "capsule"); + Validate.nonNull(fieldName, "field name"); + + Savable[] tmp = capsule.readSavableArray(fieldName, null); + Transform[] result; + if (tmp == null) { + result = null; + } else { + result = new Transform[tmp.length]; + for (int i = 0; i < tmp.length; ++i) { + result[i] = (Transform) tmp[i]; + } + } + + return result; + } + + /** + * Calculate a coordinate transform for the specified Spatial relative to a + * specified ancestor node. The result incorporates the transform of the + * starting Spatial, but not that of the ancestor. + * + * @param startSpatial the starting Spatial (not null, unaffected) + * @param ancestorNode the ancestor node (not null, unaffected) + * @param storeResult storage for the result (modified if not null) + * @return a coordinate transform (either storeResult or a new vector, not + * null) + */ + public static Transform relativeTransform( + Spatial startSpatial, Node ancestorNode, Transform storeResult) { + Validate.nonNull(startSpatial, "spatial"); + Validate.nonNull(ancestorNode, "ancestor"); + assert startSpatial.hasAncestor(ancestorNode); + Transform result + = (storeResult == null) ? new Transform() : storeResult; + + result.loadIdentity(); + Spatial loopSpatial = startSpatial; + while (loopSpatial != ancestorNode) { + Transform localTransform = loopSpatial.getLocalTransform(); // alias + MyMath.combine(result, localTransform, result); + loopSpatial = loopSpatial.getParent(); + } + + return result; + } + + /** + * Validate an Armature for use with DynamicAnimControl. + * + * @param armature the Armature to validate (not null, unaffected) + */ + public static void validate(Armature armature) { + int numJoints = armature.getJointCount(); + if (numJoints < 0) { + throw new IllegalArgumentException("Joint count is negative!"); + } + + Collection nameSet = new TreeSet<>(); + for (int jointIndex = 0; jointIndex < numJoints; ++jointIndex) { + Joint joint = armature.getJoint(jointIndex); + if (joint == null) { + String msg = String.format( + "Joint %d in armature is null!", jointIndex); + throw new IllegalArgumentException(msg); + } + String jointName = joint.getName(); + if (jointName == null) { + String message = String.format( + "Joint %d in armature has null name!", jointIndex); + throw new IllegalArgumentException(message); + } else if (jointName.equals(DacConfiguration.torsoName)) { + String message = String.format( + "Joint %d in armature has a reserved name!", + jointIndex); + throw new IllegalArgumentException(message); + } else if (nameSet.contains(jointName)) { + String message + = "Duplicate joint name in skeleton: " + jointName; + throw new IllegalArgumentException(message); + } + nameSet.add(jointName); + } + } + + /** + * Validate a Skeleton for use with DynamicAnimControl. + * + * @param skeleton the Skeleton to validate (not null, unaffected) + */ + public static void validate(Skeleton skeleton) { + int numBones = skeleton.getBoneCount(); + if (numBones < 0) { + throw new IllegalArgumentException("Bone count is negative!"); + } + + Collection nameSet = new TreeSet<>(); + for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { + Bone bone = skeleton.getBone(boneIndex); + if (bone == null) { + String msg = String.format( + "Bone %d in skeleton is null!", boneIndex); + throw new IllegalArgumentException(msg); + } + String boneName = bone.getName(); + if (boneName == null) { + String msg = String.format( + "Bone %d in skeleton has null name!", boneIndex); + throw new IllegalArgumentException(msg); + } else if (boneName.equals(DacConfiguration.torsoName)) { + String msg = String.format( + "Bone %d in skeleton has a reserved name!", boneIndex); + throw new IllegalArgumentException(msg); + } else if (nameSet.contains(boneName)) { + String msg = "Duplicate bone name in skeleton: " + + MyString.quote(boneName); + throw new IllegalArgumentException(msg); + } + nameSet.add(boneName); + } + } + + /** + * Validate a model for use with DynamicAnimControl. + * + * @param model the model to validate (not null, unaffected) + * @throws IllegalArgumentException for an invalid model + */ + public static void validate(Spatial model) { + Validate.nonNull(model, "model"); + + List geometries = MySpatial.listGeometries(model); + if (geometries.isEmpty()) { + throw new IllegalArgumentException("No meshes in the model."); + } + for (Geometry geometry : geometries) { + if (geometry.isIgnoreTransform()) { + throw new IllegalArgumentException( + "A model geometry ignores transforms."); + } + } + } + // ************************************************************************* + // private methods + + /** + * Add the vertex weights of each bone in the specified mesh to an array of + * total weights. + * + * @param mesh animated mesh to analyze (not null, unaffected) + * @param totalWeights (not null, modified) + */ + private static void addWeights(Mesh mesh, float[] totalWeights) { + assert totalWeights != null; + + int maxWeightsPerVert = mesh.getMaxNumWeights(); + if (maxWeightsPerVert <= 0) { + maxWeightsPerVert = 1; + } + assert maxWeightsPerVert > 0 : maxWeightsPerVert; + assert maxWeightsPerVert <= 4 : maxWeightsPerVert; + + VertexBuffer biBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex); + Buffer boneIndexBuffer = biBuf.getDataReadOnly(); + boneIndexBuffer.rewind(); + int numBoneIndices = boneIndexBuffer.remaining(); + assert numBoneIndices % 4 == 0 : numBoneIndices; + int numVertices = boneIndexBuffer.remaining() / 4; + + FloatBuffer weightBuffer + = mesh.getFloatBuffer(VertexBuffer.Type.BoneWeight); + weightBuffer.rewind(); + int numWeights = weightBuffer.remaining(); + assert numWeights == numVertices * 4 : numWeights; + + for (int vIndex = 0; vIndex < numVertices; ++vIndex) { + for (int wIndex = 0; wIndex < 4; ++wIndex) { + float weight = weightBuffer.get(); + int boneIndex = MyBuffer.readIndex(boneIndexBuffer); + if (wIndex < maxWeightsPerVert) { + totalWeights[boneIndex] += FastMath.abs(weight); + } + } + } + } + + /** + * Calculate the total mesh weight animated by each Joint in the specified + * meshes. + * + * @param meshes the animated meshes to analyze (not null, unaffected) + * @param armature (not null, unaffected) + * @return a map from joint indices to total mesh weight + */ + private static float[] totalWeights(Mesh[] meshes, Armature armature) { + Validate.nonNull(meshes, "meshes"); + + int numBones = armature.getJointCount(); + float[] result = new float[numBones]; + for (Mesh mesh : meshes) { + addWeights(mesh, result); + } + + List joints = MySkeleton.preOrderJoints(armature); + Collections.reverse(joints); + for (Joint childJoint : joints) { + int childIndex = childJoint.getId(); + Joint parent = childJoint.getParent(); + if (parent != null) { + int parentIndex = parent.getId(); + result[parentIndex] += result[childIndex]; + } + } + + return result; + } + + /** + * Calculate the total mesh weight animated by each Bone in the specified + * meshes. + * + * @param meshes the animated meshes to analyze (not null, unaffected) + * @param skeleton (not null, unaffected) + * @return a map from bone indices to total mesh weight + */ + private static float[] totalWeights(Mesh[] meshes, Skeleton skeleton) { + Validate.nonNull(meshes, "meshes"); + + int numBones = skeleton.getBoneCount(); + float[] result = new float[numBones]; + for (Mesh mesh : meshes) { + addWeights(mesh, result); + } + + List bones = MySkeleton.preOrderBones(skeleton); + Collections.reverse(bones); + for (Bone childBone : bones) { + int childIndex = skeleton.getBoneIndex(childBone); + Bone parent = childBone.getParent(); + if (parent != null) { + int parentIndex = skeleton.getBoneIndex(parent); + result[parentIndex] += result[childIndex]; + } + } + + return result; + } + + /** + * Tabulate the total bone weight associated with each bone/torso link in a + * ragdoll. + * + * @param biArray the array of bone indices (not null, unaffected) + * @param bwArray the array of bone weights (not null, unaffected) + * @param managerMap a map from bone indices to managing link names (not + * null, unaffected) + * @return a new map from link names to total weight + */ + private static Map + weightMap(int[] biArray, float[] bwArray, String[] managerMap) { + assert biArray.length == 4; + assert bwArray.length == 4; + + Map weightMap = new HashMap<>(4); + for (int j = 0; j < 4; ++j) { + int boneIndex = biArray[j]; + if (boneIndex != -1) { + String managerName = managerMap[boneIndex]; + if (weightMap.containsKey(managerName)) { + float oldWeight = weightMap.get(managerName); + float newWeight = oldWeight + bwArray[j]; + weightMap.put(managerName, newWeight); + } else { + weightMap.put(managerName, bwArray[j]); + } + } + } + + return weightMap; + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/ShapeHeuristic.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/ShapeHeuristic.java index b199cca6e..f86d8e919 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/ShapeHeuristic.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/ShapeHeuristic.java @@ -1,72 +1,72 @@ -/* - * Copyright (c) 2018-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.animation; - -/** - * Enumerate algorithms to create a CollisionShape for a PhysicsLink. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum ShapeHeuristic { - // ************************************************************************* - // values - - /** - * axis-aligned bounding box - */ - AABB, - /** - * minimal bounding box (not axis-aligned) - */ - MinBox, - /** - * 4-sphere hull to fill the minimal bounding box (good for many bone links) - */ - FourSphere, - /** - * centered bounding sphere - */ - Sphere, - /** - * 2-sphere hull to approximate the minimal bounding box (good for many bone - * links) - */ - TwoSphere, - /** - * convex hull of mesh vertices (precise but relatively slow) - */ - VertexHull, - /** - * rotated cylinder - */ - Cylinder -} +/* + * Copyright (c) 2018-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.animation; + +/** + * Enumerate algorithms to create a CollisionShape for a PhysicsLink. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum ShapeHeuristic { + // ************************************************************************* + // values + + /** + * axis-aligned bounding box + */ + AABB, + /** + * minimal bounding box (not axis-aligned) + */ + MinBox, + /** + * 4-sphere hull to fill the minimal bounding box (good for many bone links) + */ + FourSphere, + /** + * centered bounding sphere + */ + Sphere, + /** + * 2-sphere hull to approximate the minimal bounding box (good for many bone + * links) + */ + TwoSphere, + /** + * convex hull of mesh vertices (precise but relatively slow) + */ + VertexHull, + /** + * rotated cylinder + */ + Cylinder +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/animation/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/animation/package-info.java index ac9b2f72a..1889437f1 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/animation/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/animation/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * A dynamic animation control and some associated classes. - */ -package com.jme3.bullet.animation; +/* + * Copyright (c) 2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * A dynamic animation control and some associated classes. + */ +package com.jme3.bullet.animation; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/package-info.java index 355d3e754..09d3537cc 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/package-info.java @@ -1,36 +1,36 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes and interfaces associated with physics-oriented collision detection - * and ray/sweep tests. - */ -package com.jme3.bullet.collision; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes and interfaces associated with physics-oriented collision detection + * and ray/sweep tests. + */ +package com.jme3.bullet.collision; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java index dc46563cb..474d14226 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java @@ -1,293 +1,293 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.collision.shapes; - -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.bullet.collision.shapes.infos.CompoundMesh; -import com.jme3.bullet.collision.shapes.infos.IndexedMesh; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Triangle; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.util.clone.Cloner; -import java.io.IOException; -import java.util.logging.Logger; -import jme3utilities.Validate; -import jme3utilities.math.MyVector3f; - -/** - * A mesh collisions shape based on Bullet's {@code btGImpactMeshShape}. - * - * Collisions between GImpactCollisionShape and PlaneCollisionShape objects are - * never detected. - * - * @author normenhansen - */ -public class GImpactCollisionShape extends CollisionShape { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger2 - = Logger.getLogger(GImpactCollisionShape.class.getName()); - /** - * field names for serialization - */ - final private static String tagNativeMesh = "nativeMesh"; - // ************************************************************************* - // fields - - /** - * native mesh used to construct this shape - */ - private CompoundMesh nativeMesh; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected GImpactCollisionShape() { - } - - /** - * Instantiate a shape based on the specified CompoundMesh and offset. - * - * @param mesh the mesh on which to base the shape (not null, unaffected) - * @param offset the offset to add to the vertex positions (not null, - * unaffected) - */ - public GImpactCollisionShape(CompoundMesh mesh, Vector3f offset) { - this.nativeMesh = new CompoundMesh(mesh, offset); - createShape(); - } - - /** - * Instantiate a shape based on the specified native mesh(es). - * - * @param submeshes the mesh(es) on which to base the shape (not null) - */ - public GImpactCollisionShape(IndexedMesh... submeshes) { - this.nativeMesh = new CompoundMesh(); - for (IndexedMesh submesh : submeshes) { - nativeMesh.add(submesh); - } - createShape(); - } - - /** - * Instantiate a shape based on the specified JME mesh(es). - * - * @param jmeMeshes the mesh(es) on which to base the shape (not null, - * unaffected) - */ - public GImpactCollisionShape(Mesh... jmeMeshes) { - this.nativeMesh = new CompoundMesh(jmeMeshes); - createShape(); - } - // ************************************************************************* - // new methods exposed - - /** - * Count how many triangles are in the mesh. - * - * @return the count (≥0) - */ - public int countMeshTriangles() { - int result = nativeMesh.countTriangles(); - return result; - } - - /** - * Count how many vertices are in the mesh. - * - * @return the count (≥0) - */ - public int countMeshVertices() { - int numVertices = nativeMesh.countVertices(); - return numVertices; - } - - /** - * Attempt to divide this shape into 2 shapes. - * - * @param splittingTriangle to define the splitting plane (in shape - * coordinates, not null, unaffected) - * @return a pair of children, the first child generated by the plane's - * minus side and the 2nd child generated by its plus side; either child may - * be null, indicating an empty shape - */ - public ChildCollisionShape[] split(Triangle splittingTriangle) { - Validate.nonNull(splittingTriangle, "splitting triangle"); - - CompoundMesh[] mp = nativeMesh.split(splittingTriangle); - ChildCollisionShape[] result = new ChildCollisionShape[2]; - int numMinus = (mp[0] == null) ? 0 : mp[0].countTriangles(); - int numPlus = (mp[1] == null) ? 0 : mp[1].countTriangles(); - if (numMinus == 0 || numPlus == 0) { - // Degenerate case: all triangles lie to one side of the plane. - ChildCollisionShape child - = new ChildCollisionShape(new Vector3f(), this); - if (numMinus > 0) { - result[0] = child; - } else if (numPlus > 0) { - result[1] = child; - } - - } else { - Vector3f max = new Vector3f(); - Vector3f min = new Vector3f(); - Vector3f offset = max; // alias - Vector3f center = min; // alias - for (int i = 0; i < 2; ++i) { - mp[i].maxMin(max, min); - MyVector3f.midpoint(max, min, center); - offset.set(center).negateLocal(); - GImpactCollisionShape shape - = new GImpactCollisionShape(mp[i], offset); - shape.setScale(scale); - result[i] = new ChildCollisionShape(center, shape); - } - } - - return result; - } - // ************************************************************************* - // CollisionShape methods - - /** - * Test whether this shape can be split by an arbitrary plane. - * - * @return true if splittable, false otherwise - */ - @Override - public boolean canSplit() { - return true; - } - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned shape into a deep-cloned one, using the specified Cloner - * and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this shape (not null) - * @param original the instance from which this shape was shallow-cloned - * (not null, unaffected) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - super.cloneFields(cloner, original); - - this.nativeMesh = cloner.clone(nativeMesh); - createShape(); - } - - /** - * De-serialize this shape from the specified importer, for example when - * loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - super.read(importer); - InputCapsule capsule = importer.getCapsule(this); - this.nativeMesh - = (CompoundMesh) capsule.readSavable(tagNativeMesh, null); - createShape(); - } - - /** - * Recalculate this shape's bounding box if necessary. - */ - @Override - protected void recalculateAabb() { - long shapeId = nativeId(); - recalcAabb(shapeId); - } - - /** - * Alter the scale factors of this shape. - *

- * Note that if the shape is shared (between collision objects and/or - * compound shapes) changes can have unintended consequences. - * - * @param scale the desired scale factor for each local axis (not null, no - * negative component, unaffected, default=(1,1,1)) - */ - @Override - public void setScale(Vector3f scale) { - super.setScale(scale); - recalculateAabb(); - } - - /** - * Serialize this shape to the specified exporter, for example when saving - * to a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - super.write(exporter); - OutputCapsule capsule = exporter.getCapsule(this); - capsule.write(nativeMesh, tagNativeMesh, null); - } - // ************************************************************************* - // Java private methods - - /** - * Instantiate the configured {@code btGImpactMeshShape}. - */ - private void createShape() { - long meshId = nativeMesh.nativeId(); - long shapeId = createShape(meshId); - setNativeId(shapeId); - - setContactFilterEnabled(enableContactFilter); - setScale(scale); - setMargin(margin); - } - // ************************************************************************* - // native private methods - - native private static long createShape(long meshId); - - native private static void recalcAabb(long shapeId); -} +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.bullet.collision.shapes.infos.CompoundMesh; +import com.jme3.bullet.collision.shapes.infos.IndexedMesh; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.util.clone.Cloner; +import java.io.IOException; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.math.MyVector3f; + +/** + * A mesh collisions shape based on Bullet's {@code btGImpactMeshShape}. + * + * Collisions between GImpactCollisionShape and PlaneCollisionShape objects are + * never detected. + * + * @author normenhansen + */ +public class GImpactCollisionShape extends CollisionShape { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger2 + = Logger.getLogger(GImpactCollisionShape.class.getName()); + /** + * field names for serialization + */ + final private static String tagNativeMesh = "nativeMesh"; + // ************************************************************************* + // fields + + /** + * native mesh used to construct this shape + */ + private CompoundMesh nativeMesh; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected GImpactCollisionShape() { + } + + /** + * Instantiate a shape based on the specified CompoundMesh and offset. + * + * @param mesh the mesh on which to base the shape (not null, unaffected) + * @param offset the offset to add to the vertex positions (not null, + * unaffected) + */ + public GImpactCollisionShape(CompoundMesh mesh, Vector3f offset) { + this.nativeMesh = new CompoundMesh(mesh, offset); + createShape(); + } + + /** + * Instantiate a shape based on the specified native mesh(es). + * + * @param submeshes the mesh(es) on which to base the shape (not null) + */ + public GImpactCollisionShape(IndexedMesh... submeshes) { + this.nativeMesh = new CompoundMesh(); + for (IndexedMesh submesh : submeshes) { + nativeMesh.add(submesh); + } + createShape(); + } + + /** + * Instantiate a shape based on the specified JME mesh(es). + * + * @param jmeMeshes the mesh(es) on which to base the shape (not null, + * unaffected) + */ + public GImpactCollisionShape(Mesh... jmeMeshes) { + this.nativeMesh = new CompoundMesh(jmeMeshes); + createShape(); + } + // ************************************************************************* + // new methods exposed + + /** + * Count how many triangles are in the mesh. + * + * @return the count (≥0) + */ + public int countMeshTriangles() { + int result = nativeMesh.countTriangles(); + return result; + } + + /** + * Count how many vertices are in the mesh. + * + * @return the count (≥0) + */ + public int countMeshVertices() { + int numVertices = nativeMesh.countVertices(); + return numVertices; + } + + /** + * Attempt to divide this shape into 2 shapes. + * + * @param splittingTriangle to define the splitting plane (in shape + * coordinates, not null, unaffected) + * @return a pair of children, the first child generated by the plane's + * minus side and the 2nd child generated by its plus side; either child may + * be null, indicating an empty shape + */ + public ChildCollisionShape[] split(Triangle splittingTriangle) { + Validate.nonNull(splittingTriangle, "splitting triangle"); + + CompoundMesh[] mp = nativeMesh.split(splittingTriangle); + ChildCollisionShape[] result = new ChildCollisionShape[2]; + int numMinus = (mp[0] == null) ? 0 : mp[0].countTriangles(); + int numPlus = (mp[1] == null) ? 0 : mp[1].countTriangles(); + if (numMinus == 0 || numPlus == 0) { + // Degenerate case: all triangles lie to one side of the plane. + ChildCollisionShape child + = new ChildCollisionShape(new Vector3f(), this); + if (numMinus > 0) { + result[0] = child; + } else if (numPlus > 0) { + result[1] = child; + } + + } else { + Vector3f max = new Vector3f(); + Vector3f min = new Vector3f(); + Vector3f offset = max; // alias + Vector3f center = min; // alias + for (int i = 0; i < 2; ++i) { + mp[i].maxMin(max, min); + MyVector3f.midpoint(max, min, center); + offset.set(center).negateLocal(); + GImpactCollisionShape shape + = new GImpactCollisionShape(mp[i], offset); + shape.setScale(scale); + result[i] = new ChildCollisionShape(center, shape); + } + } + + return result; + } + // ************************************************************************* + // CollisionShape methods + + /** + * Test whether this shape can be split by an arbitrary plane. + * + * @return true if splittable, false otherwise + */ + @Override + public boolean canSplit() { + return true; + } + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned shape into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this shape (not null) + * @param original the instance from which this shape was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + + this.nativeMesh = cloner.clone(nativeMesh); + createShape(); + } + + /** + * De-serialize this shape from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + this.nativeMesh + = (CompoundMesh) capsule.readSavable(tagNativeMesh, null); + createShape(); + } + + /** + * Recalculate this shape's bounding box if necessary. + */ + @Override + protected void recalculateAabb() { + long shapeId = nativeId(); + recalcAabb(shapeId); + } + + /** + * Alter the scale factors of this shape. + *

+ * Note that if the shape is shared (between collision objects and/or + * compound shapes) changes can have unintended consequences. + * + * @param scale the desired scale factor for each local axis (not null, no + * negative component, unaffected, default=(1,1,1)) + */ + @Override + public void setScale(Vector3f scale) { + super.setScale(scale); + recalculateAabb(); + } + + /** + * Serialize this shape to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + capsule.write(nativeMesh, tagNativeMesh, null); + } + // ************************************************************************* + // Java private methods + + /** + * Instantiate the configured {@code btGImpactMeshShape}. + */ + private void createShape() { + long meshId = nativeMesh.nativeId(); + long shapeId = createShape(meshId); + setNativeId(shapeId); + + setContactFilterEnabled(enableContactFilter); + setScale(scale); + setMargin(margin); + } + // ************************************************************************* + // native private methods + + native private static long createShape(long meshId); + + native private static void recalcAabb(long shapeId); +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java index e60e91992..83c5bd460 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java @@ -1,462 +1,462 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.collision.shapes; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.infos.IndexedMesh; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.terrain.Terrain; -import com.jme3.terrain.heightmap.HeightMap; -import com.jme3.util.BufferUtils; -import com.jme3.util.clone.Cloner; -import java.io.IOException; -import java.nio.FloatBuffer; -import java.util.logging.Logger; -import jme3utilities.Validate; - -/** - * A collision shape for terrain defined by a matrix of height values, based on - * Bullet's {@code btHeightfieldTerrainShape}. Should be more efficient than an - * equivalent MeshCollisionShape. Not for use in dynamic bodies. Collisions - * between HeightfieldCollisionShape, MeshCollisionShape, and - * PlaneCollisionShape objects are never detected. - * - * @author Brent Owens - */ -public class HeightfieldCollisionShape extends CollisionShape { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger2 - = Logger.getLogger(HeightfieldCollisionShape.class.getName()); - /** - * field names for serialization - */ - final private static String tagFlipQuadEdges = "flipQuadEdges"; - final private static String tagFlipTriangleWinding = "flipTriangleWinding"; - final private static String tagHeightfieldData = "heightfieldData"; - final private static String tagHeightScale = "heightScale"; - final private static String tagHeightStickLength = "heightStickLength"; - final private static String tagHeightStickWidth = "heightStickWidth"; - final private static String tagMaxHeight = "maxHeight"; - final private static String tagMinHeight = "minHeight"; - final private static String tagUpAxis = "upAxis"; - final private static String tagUseDiamond = "useDiamond"; - final private static String tagUseZigzag = "useZigzag"; - /** - * local copy of {@link com.jme3.math.Vector3f#UNIT_XYZ} - */ - final private static Vector3f scaleIdentity = new Vector3f(1f, 1f, 1f); - // ************************************************************************* - // fields - - /** - * reverse the direction of the first diagonal - */ - private boolean flipQuadEdges = true; - /** - * true→left-hand winding of triangles - */ - private boolean flipTriangleWinding = false; - /** - * true→diagonals alternate on both horizontal axes - */ - private boolean useDiamond = false; - /** - * true→diagonals alternate on one horizontal axis - */ - private boolean useZigzag = false; - /** - * scale factor for Bullet to apply to the heightfield - */ - private float heightScale = 1f; - /** - * highest sample in the heightfield or -minHeight, whichever is higher - */ - private float maxHeight; - /** - * lowest sample in the heightfield or -maxHeight, whichever is lower - */ - private float minHeight; - /** - * array of heightfield samples - */ - private float[] heightfieldData; - /** - * direct buffer for passing height data to Bullet - *

- * A Java reference must persist after createShape() completes, or else the - * buffer might get garbage collected. - */ - private FloatBuffer directBuffer; - /** - * copy of number of columns in the heightfield (>1) - */ - private int heightStickLength; - /** - * copy of number of rows in the heightfield (>1) - */ - private int heightStickWidth; - /** - * copy of the height-axis index (0→X, 1→Y, 2→Z) - */ - private int upAxis = PhysicsSpace.AXIS_Y; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected HeightfieldCollisionShape() { - } - - /** - * Instantiate a square shape for the specified height map. - * - * @param heightmap (not null, length≥4, length a perfect square, - * unaffected) - */ - public HeightfieldCollisionShape(float[] heightmap) { - Validate.nonEmpty(heightmap, "heightmap"); - assert heightmap.length >= 4 : heightmap.length; - - createCollisionHeightfield(heightmap, scaleIdentity); - } - - /** - * Instantiate a square shape for the specified HeightMap. If the HeightMap - * isn't populated, invoke its load() method. - * - * @param heightMap (not null, size ≥ 2) - */ - public HeightfieldCollisionShape(HeightMap heightMap) { - float[] array = heightMap.getHeightMap(); - if (array == null) { // not populated - boolean success = heightMap.load(); - assert success; - array = heightMap.getHeightMap(); - assert array != null; - } - assert array.length >= 4 : array.length; - - createCollisionHeightfield(array, scaleIdentity); - } - - /** - * Instantiate a square shape for the specified height map and scale vector. - * - * @param heightmap (not null, length≥4, length a perfect square, - * unaffected) - * @param scale the desired scale factor for each local axis (not null, no - * negative component, unaffected, default=(1,1,1)) - */ - public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) { - Validate.nonEmpty(heightmap, "heightmap"); - assert heightmap.length >= 4 : heightmap.length; - Validate.nonNegative(scale, "scale"); - - createCollisionHeightfield(heightmap, scale); - } - - /** - * Instantiate a square shape for the specified terrain and scale vector. - * - * @param terrain (not null, size ≥2, unaffected) - * @param scale the desired scale factor for each local axis (not null, no - * negative component, unaffected, default=(1,1,1)) - */ - public HeightfieldCollisionShape(Terrain terrain, Vector3f scale) { - Validate.nonNegative(scale, "scale"); - Validate.inRange(terrain.getTerrainSize(), "terrain size", 2, - Integer.MAX_VALUE); - - float[] heightmap = terrain.getHeightMap(); - assert heightmap.length >= 4 : heightmap.length; - createCollisionHeightfield(heightmap, scale); - } - - /** - * Instantiate a rectangular shape for the specified parameters. - * - * @param stickLength the number of rows in the heightfield (>1) - * @param stickWidth number of columns in the heightfield (>1) - * @param heightmap (not null, length≥stickLength*stickWidth, unaffected) - * @param scale the desired scale factor for each local axis (not null, no - * negative component, unaffected, default=(1,1,1)) - * @param upAxis the height-axis index (0→X, 1→Y, 2→Z, - * default=1) - * @param flipQuadEdges true→reverse the direction of the first - * diagonal (default=true) - * @param flipTriangleWinding true→left-hand winding of triangles - * (default=false) - * @param useDiamond true→diagonals alternate on both horizontal axes - * (default=false) - * @param useZigzag true→diagonals alternate on one horizontal axis - * (default=false) - */ - public HeightfieldCollisionShape(int stickLength, int stickWidth, - float[] heightmap, Vector3f scale, int upAxis, - boolean flipQuadEdges, boolean flipTriangleWinding, - boolean useDiamond, boolean useZigzag) { - Validate.inRange(stickLength, "stick length", 2, Integer.MAX_VALUE); - Validate.inRange(stickWidth, "stick width", 2, Integer.MAX_VALUE); - Validate.nonEmpty(heightmap, "heightmap"); - assert heightmap.length >= stickLength * stickWidth : heightmap.length; - Validate.nonNegative(scale, "scale"); - Validate.axisIndex(upAxis, "up axis"); - - this.heightStickLength = stickLength; - this.heightStickWidth = stickWidth; - this.heightfieldData = heightmap.clone(); - this.scale.set(scale); - this.upAxis = upAxis; - this.flipQuadEdges = flipQuadEdges; - this.flipTriangleWinding = flipTriangleWinding; - this.useDiamond = useDiamond; - this.useZigzag = useZigzag; - - calculateMinAndMax(); - createShape(); - } - // ************************************************************************* - // new methods exposed - - /** - * Count how many data points are in the heightfield. - * - * @return the count (>0) - */ - public int countMeshVertices() { - int count = heightfieldData.length; - - assert count > 0 : count; - return count; - } - // ************************************************************************* - // CollisionShape methods - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned shape into a deep-cloned one, using the specified Cloner - * and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this shape (not null) - * @param original the instance from which this shape was shallow-cloned - * (not null, unaffected) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - super.cloneFields(cloner, original); - // directBuffer and heightfieldData are never cloned. - createShape(); - } - - /** - * De-serialize this shape from the specified importer, for example when - * loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - super.read(importer); - InputCapsule capsule = importer.getCapsule(this); - - this.heightStickWidth = capsule.readInt(tagHeightStickWidth, 0); - this.heightStickLength = capsule.readInt(tagHeightStickLength, 0); - this.heightScale = capsule.readFloat(tagHeightScale, 0f); - this.minHeight = capsule.readFloat(tagMinHeight, 0f); - this.maxHeight = capsule.readFloat(tagMaxHeight, 0f); - this.upAxis = capsule.readInt(tagUpAxis, PhysicsSpace.AXIS_Y); - this.heightfieldData - = capsule.readFloatArray(tagHeightfieldData, new float[0]); - this.flipQuadEdges = capsule.readBoolean(tagFlipQuadEdges, true); - this.flipTriangleWinding - = capsule.readBoolean(tagFlipTriangleWinding, false); - this.useDiamond = capsule.readBoolean(tagUseDiamond, false); - this.useZigzag = capsule.readBoolean(tagUseZigzag, false); - - createShape(); - } - - /** - * Approximate this shape with a splittable shape. - * - * @return a new splittable shape - */ - @Override - public CollisionShape toSplittableShape() { - // Generate debug triangles. - FloatBuffer buffer = DebugShapeFactory.getDebugTriangles( - this, DebugShapeFactory.lowResolution); - IndexedMesh nativeMesh = new IndexedMesh(buffer); - MeshCollisionShape result = new MeshCollisionShape(true, nativeMesh); - - return result; - } - - /** - * Serialize this shape to the specified exporter, for example when saving - * to a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - super.write(exporter); - OutputCapsule capsule = exporter.getCapsule(this); - - capsule.write(heightStickWidth, tagHeightStickWidth, 0); - capsule.write(heightStickLength, tagHeightStickLength, 0); - capsule.write(heightScale, tagHeightScale, 0f); - capsule.write(minHeight, tagMinHeight, 0f); - capsule.write(maxHeight, tagMaxHeight, 0f); - capsule.write(upAxis, tagUpAxis, PhysicsSpace.AXIS_Y); - capsule.write(heightfieldData, tagHeightfieldData, new float[0]); - capsule.write(flipQuadEdges, tagFlipQuadEdges, true); - capsule.write(flipTriangleWinding, tagFlipTriangleWinding, false); - capsule.write(useDiamond, tagUseDiamond, false); - capsule.write(useZigzag, tagUseZigzag, false); - } - // ************************************************************************* - // Java private methods - - /** - * Calculate min and max heights for the heightfield data. - */ - private void calculateMinAndMax() { - int elements = heightStickLength * heightStickWidth; - assert elements == heightfieldData.length : heightfieldData.length; - - float min = heightfieldData[0]; - float max = heightfieldData[0]; - - // Find the min and max heights in the data. - for (float height : heightfieldData) { - if (height < min) { - min = height; - } - if (height > max) { - max = height; - } - } - /* - * Center the terrain's bounding box at y=0 by setting the - * min and max height to have equal magnitudes and opposite signs. - * Otherwise, the collision shape won't match the rendered heights. - */ - if (max < 0) { - max = -min; - } else if (Math.abs(max) > Math.abs(min)) { - min = -max; - } else { - max = -min; - } - this.minHeight = min; - this.maxHeight = max; - } - - /** - * Instantiate a square {@code btHeightfieldTerrainShape}. - * - * @param heightmap (not null, length≥4, length a perfect square, - * unaffected) - * @param worldScale the desired scale factor for each local axis (not null, - * no negative component, unaffected) - */ - private void - createCollisionHeightfield(float[] heightmap, Vector3f worldScale) { - scale.set(worldScale); - - this.heightfieldData = heightmap.clone(); - this.heightStickWidth = (int) FastMath.sqrt(heightfieldData.length); - assert heightStickWidth > 1 : heightStickWidth; - - this.heightStickLength = heightStickWidth; - - calculateMinAndMax(); - createShape(); - } - - /** - * Instantiate the configured {@code btHeightfieldTerrainShape}. - */ - private void createShape() { - this.directBuffer - = BufferUtils.createFloatBuffer(heightfieldData.length); - for (float height : heightfieldData) { - if (!Float.isFinite(height)) { - throw new IllegalArgumentException("illegal height: " + height); - } - directBuffer.put(height); - } - - long shapeId = createShape2(heightStickWidth, heightStickLength, - directBuffer, heightScale, minHeight, maxHeight, upAxis, - flipQuadEdges, flipTriangleWinding, useDiamond, useZigzag); - setNativeId(shapeId); - - setContactFilterEnabled(enableContactFilter); - setScale(scale); - setMargin(margin); - } - - /** - * Free the identified tracked native object. Invoked by reflection. - * - * @param shapeId the native identifier (not zero) - */ - private static void freeNativeObject(long shapeId) { - assert shapeId != 0L; - finalizeNative(shapeId); - } - // ************************************************************************* - // native private methods - - native private static long createShape2(int stickWidth, int stickLength, - FloatBuffer heightfieldData, float heightScale, float minHeight, - float maxHeight, int upAxis, boolean flipQuadEdges, - boolean flipTriangleWinding, boolean useDiamond, boolean useZigzag); - - native private static void finalizeNative(long shapeId); -} +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.infos.IndexedMesh; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.terrain.Terrain; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.util.BufferUtils; +import com.jme3.util.clone.Cloner; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.logging.Logger; +import jme3utilities.Validate; + +/** + * A collision shape for terrain defined by a matrix of height values, based on + * Bullet's {@code btHeightfieldTerrainShape}. Should be more efficient than an + * equivalent MeshCollisionShape. Not for use in dynamic bodies. Collisions + * between HeightfieldCollisionShape, MeshCollisionShape, and + * PlaneCollisionShape objects are never detected. + * + * @author Brent Owens + */ +public class HeightfieldCollisionShape extends CollisionShape { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger2 + = Logger.getLogger(HeightfieldCollisionShape.class.getName()); + /** + * field names for serialization + */ + final private static String tagFlipQuadEdges = "flipQuadEdges"; + final private static String tagFlipTriangleWinding = "flipTriangleWinding"; + final private static String tagHeightfieldData = "heightfieldData"; + final private static String tagHeightScale = "heightScale"; + final private static String tagHeightStickLength = "heightStickLength"; + final private static String tagHeightStickWidth = "heightStickWidth"; + final private static String tagMaxHeight = "maxHeight"; + final private static String tagMinHeight = "minHeight"; + final private static String tagUpAxis = "upAxis"; + final private static String tagUseDiamond = "useDiamond"; + final private static String tagUseZigzag = "useZigzag"; + /** + * local copy of {@link com.jme3.math.Vector3f#UNIT_XYZ} + */ + final private static Vector3f scaleIdentity = new Vector3f(1f, 1f, 1f); + // ************************************************************************* + // fields + + /** + * reverse the direction of the first diagonal + */ + private boolean flipQuadEdges = true; + /** + * true→left-hand winding of triangles + */ + private boolean flipTriangleWinding = false; + /** + * true→diagonals alternate on both horizontal axes + */ + private boolean useDiamond = false; + /** + * true→diagonals alternate on one horizontal axis + */ + private boolean useZigzag = false; + /** + * scale factor for Bullet to apply to the heightfield + */ + private float heightScale = 1f; + /** + * highest sample in the heightfield or -minHeight, whichever is higher + */ + private float maxHeight; + /** + * lowest sample in the heightfield or -maxHeight, whichever is lower + */ + private float minHeight; + /** + * array of heightfield samples + */ + private float[] heightfieldData; + /** + * direct buffer for passing height data to Bullet + *

+ * A Java reference must persist after createShape() completes, or else the + * buffer might get garbage collected. + */ + private FloatBuffer directBuffer; + /** + * copy of number of columns in the heightfield (>1) + */ + private int heightStickLength; + /** + * copy of number of rows in the heightfield (>1) + */ + private int heightStickWidth; + /** + * copy of the height-axis index (0→X, 1→Y, 2→Z) + */ + private int upAxis = PhysicsSpace.AXIS_Y; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected HeightfieldCollisionShape() { + } + + /** + * Instantiate a square shape for the specified height map. + * + * @param heightmap (not null, length≥4, length a perfect square, + * unaffected) + */ + public HeightfieldCollisionShape(float[] heightmap) { + Validate.nonEmpty(heightmap, "heightmap"); + assert heightmap.length >= 4 : heightmap.length; + + createCollisionHeightfield(heightmap, scaleIdentity); + } + + /** + * Instantiate a square shape for the specified HeightMap. If the HeightMap + * isn't populated, invoke its load() method. + * + * @param heightMap (not null, size ≥ 2) + */ + public HeightfieldCollisionShape(HeightMap heightMap) { + float[] array = heightMap.getHeightMap(); + if (array == null) { // not populated + boolean success = heightMap.load(); + assert success; + array = heightMap.getHeightMap(); + assert array != null; + } + assert array.length >= 4 : array.length; + + createCollisionHeightfield(array, scaleIdentity); + } + + /** + * Instantiate a square shape for the specified height map and scale vector. + * + * @param heightmap (not null, length≥4, length a perfect square, + * unaffected) + * @param scale the desired scale factor for each local axis (not null, no + * negative component, unaffected, default=(1,1,1)) + */ + public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) { + Validate.nonEmpty(heightmap, "heightmap"); + assert heightmap.length >= 4 : heightmap.length; + Validate.nonNegative(scale, "scale"); + + createCollisionHeightfield(heightmap, scale); + } + + /** + * Instantiate a square shape for the specified terrain and scale vector. + * + * @param terrain (not null, size ≥2, unaffected) + * @param scale the desired scale factor for each local axis (not null, no + * negative component, unaffected, default=(1,1,1)) + */ + public HeightfieldCollisionShape(Terrain terrain, Vector3f scale) { + Validate.nonNegative(scale, "scale"); + Validate.inRange(terrain.getTerrainSize(), "terrain size", 2, + Integer.MAX_VALUE); + + float[] heightmap = terrain.getHeightMap(); + assert heightmap.length >= 4 : heightmap.length; + createCollisionHeightfield(heightmap, scale); + } + + /** + * Instantiate a rectangular shape for the specified parameters. + * + * @param stickLength the number of rows in the heightfield (>1) + * @param stickWidth number of columns in the heightfield (>1) + * @param heightmap (not null, length≥stickLength*stickWidth, unaffected) + * @param scale the desired scale factor for each local axis (not null, no + * negative component, unaffected, default=(1,1,1)) + * @param upAxis the height-axis index (0→X, 1→Y, 2→Z, + * default=1) + * @param flipQuadEdges true→reverse the direction of the first + * diagonal (default=true) + * @param flipTriangleWinding true→left-hand winding of triangles + * (default=false) + * @param useDiamond true→diagonals alternate on both horizontal axes + * (default=false) + * @param useZigzag true→diagonals alternate on one horizontal axis + * (default=false) + */ + public HeightfieldCollisionShape(int stickLength, int stickWidth, + float[] heightmap, Vector3f scale, int upAxis, + boolean flipQuadEdges, boolean flipTriangleWinding, + boolean useDiamond, boolean useZigzag) { + Validate.inRange(stickLength, "stick length", 2, Integer.MAX_VALUE); + Validate.inRange(stickWidth, "stick width", 2, Integer.MAX_VALUE); + Validate.nonEmpty(heightmap, "heightmap"); + assert heightmap.length >= stickLength * stickWidth : heightmap.length; + Validate.nonNegative(scale, "scale"); + Validate.axisIndex(upAxis, "up axis"); + + this.heightStickLength = stickLength; + this.heightStickWidth = stickWidth; + this.heightfieldData = heightmap.clone(); + this.scale.set(scale); + this.upAxis = upAxis; + this.flipQuadEdges = flipQuadEdges; + this.flipTriangleWinding = flipTriangleWinding; + this.useDiamond = useDiamond; + this.useZigzag = useZigzag; + + calculateMinAndMax(); + createShape(); + } + // ************************************************************************* + // new methods exposed + + /** + * Count how many data points are in the heightfield. + * + * @return the count (>0) + */ + public int countMeshVertices() { + int count = heightfieldData.length; + + assert count > 0 : count; + return count; + } + // ************************************************************************* + // CollisionShape methods + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned shape into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this shape (not null) + * @param original the instance from which this shape was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + // directBuffer and heightfieldData are never cloned. + createShape(); + } + + /** + * De-serialize this shape from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + this.heightStickWidth = capsule.readInt(tagHeightStickWidth, 0); + this.heightStickLength = capsule.readInt(tagHeightStickLength, 0); + this.heightScale = capsule.readFloat(tagHeightScale, 0f); + this.minHeight = capsule.readFloat(tagMinHeight, 0f); + this.maxHeight = capsule.readFloat(tagMaxHeight, 0f); + this.upAxis = capsule.readInt(tagUpAxis, PhysicsSpace.AXIS_Y); + this.heightfieldData + = capsule.readFloatArray(tagHeightfieldData, new float[0]); + this.flipQuadEdges = capsule.readBoolean(tagFlipQuadEdges, true); + this.flipTriangleWinding + = capsule.readBoolean(tagFlipTriangleWinding, false); + this.useDiamond = capsule.readBoolean(tagUseDiamond, false); + this.useZigzag = capsule.readBoolean(tagUseZigzag, false); + + createShape(); + } + + /** + * Approximate this shape with a splittable shape. + * + * @return a new splittable shape + */ + @Override + public CollisionShape toSplittableShape() { + // Generate debug triangles. + FloatBuffer buffer = DebugShapeFactory.getDebugTriangles( + this, DebugShapeFactory.lowResolution); + IndexedMesh nativeMesh = new IndexedMesh(buffer); + MeshCollisionShape result = new MeshCollisionShape(true, nativeMesh); + + return result; + } + + /** + * Serialize this shape to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(heightStickWidth, tagHeightStickWidth, 0); + capsule.write(heightStickLength, tagHeightStickLength, 0); + capsule.write(heightScale, tagHeightScale, 0f); + capsule.write(minHeight, tagMinHeight, 0f); + capsule.write(maxHeight, tagMaxHeight, 0f); + capsule.write(upAxis, tagUpAxis, PhysicsSpace.AXIS_Y); + capsule.write(heightfieldData, tagHeightfieldData, new float[0]); + capsule.write(flipQuadEdges, tagFlipQuadEdges, true); + capsule.write(flipTriangleWinding, tagFlipTriangleWinding, false); + capsule.write(useDiamond, tagUseDiamond, false); + capsule.write(useZigzag, tagUseZigzag, false); + } + // ************************************************************************* + // Java private methods + + /** + * Calculate min and max heights for the heightfield data. + */ + private void calculateMinAndMax() { + int elements = heightStickLength * heightStickWidth; + assert elements == heightfieldData.length : heightfieldData.length; + + float min = heightfieldData[0]; + float max = heightfieldData[0]; + + // Find the min and max heights in the data. + for (float height : heightfieldData) { + if (height < min) { + min = height; + } + if (height > max) { + max = height; + } + } + /* + * Center the terrain's bounding box at y=0 by setting the + * min and max height to have equal magnitudes and opposite signs. + * Otherwise, the collision shape won't match the rendered heights. + */ + if (max < 0) { + max = -min; + } else if (Math.abs(max) > Math.abs(min)) { + min = -max; + } else { + max = -min; + } + this.minHeight = min; + this.maxHeight = max; + } + + /** + * Instantiate a square {@code btHeightfieldTerrainShape}. + * + * @param heightmap (not null, length≥4, length a perfect square, + * unaffected) + * @param worldScale the desired scale factor for each local axis (not null, + * no negative component, unaffected) + */ + private void + createCollisionHeightfield(float[] heightmap, Vector3f worldScale) { + scale.set(worldScale); + + this.heightfieldData = heightmap.clone(); + this.heightStickWidth = (int) FastMath.sqrt(heightfieldData.length); + assert heightStickWidth > 1 : heightStickWidth; + + this.heightStickLength = heightStickWidth; + + calculateMinAndMax(); + createShape(); + } + + /** + * Instantiate the configured {@code btHeightfieldTerrainShape}. + */ + private void createShape() { + this.directBuffer + = BufferUtils.createFloatBuffer(heightfieldData.length); + for (float height : heightfieldData) { + if (!Float.isFinite(height)) { + throw new IllegalArgumentException("illegal height: " + height); + } + directBuffer.put(height); + } + + long shapeId = createShape2(heightStickWidth, heightStickLength, + directBuffer, heightScale, minHeight, maxHeight, upAxis, + flipQuadEdges, flipTriangleWinding, useDiamond, useZigzag); + setNativeId(shapeId); + + setContactFilterEnabled(enableContactFilter); + setScale(scale); + setMargin(margin); + } + + /** + * Free the identified tracked native object. Invoked by reflection. + * + * @param shapeId the native identifier (not zero) + */ + private static void freeNativeObject(long shapeId) { + assert shapeId != 0L; + finalizeNative(shapeId); + } + // ************************************************************************* + // native private methods + + native private static long createShape2(int stickWidth, int stickLength, + FloatBuffer heightfieldData, float heightScale, float minHeight, + float maxHeight, int upAxis, boolean flipQuadEdges, + boolean flipTriangleWinding, boolean useDiamond, boolean useZigzag); + + native private static void finalizeNative(long shapeId); +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java index 5b0010c0d..8e7123d4d 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java @@ -1,674 +1,674 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.collision.shapes; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.FastMath; -import com.jme3.math.Plane; -import com.jme3.math.Triangle; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.util.BufferUtils; -import com.jme3.util.clone.Cloner; -import java.io.IOException; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.logging.Logger; -import jme3utilities.Validate; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyMath; -import jme3utilities.math.MyVector3f; -import jme3utilities.math.RectangularSolid; -import jme3utilities.math.VectorSet; -import jme3utilities.math.VectorSetUsingBuffer; -import vhacd.VHACDHull; -import vhacd4.Vhacd4Hull; - -/** - * A convex-hull collision shape based on Bullet's {@code btConvexHullShape}. - * For a 2-D convex hull, use Convex2dShape. - */ -public class HullCollisionShape extends ConvexShape { - // ************************************************************************* - // constants and loggers - - /** - * number of axes in a vector - */ - final private static int numAxes = 3; - /** - * message logger for this class - */ - final public static Logger logger2 - = Logger.getLogger(HullCollisionShape.class.getName()); - /** - * field names for serialization - */ - final private static String tagHullMesh = "hullMesh"; - final private static String tagPoints = "points"; - // ************************************************************************* - // fields - - /** - * direct buffer for passing vertices to Bullet - *

- * A Java reference must persist after createShape() completes, or else the - * buffer might get garbage collected. - */ - private FloatBuffer directBuffer; - /** - * array of mesh coordinates (not null, not empty, length a multiple of 3) - */ - private float[] points; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected HullCollisionShape() { - } - - /** - * Instantiate a shape based on the specified collection of locations. For - * best performance and stability, the convex hull should have no more than - * 100 vertices. - * - * @param locations a collection of location vectors on which to base the - * shape (not null, not empty, unaffected) - */ - public HullCollisionShape(Collection locations) { - Validate.nonEmpty(locations, "locations"); - - int numLocations = locations.size(); - this.points = new float[numAxes * numLocations]; - int j = 0; - for (Vector3f location : locations) { - this.points[j + PhysicsSpace.AXIS_X] = location.x; - this.points[j + PhysicsSpace.AXIS_Y] = location.y; - this.points[j + PhysicsSpace.AXIS_Z] = location.z; - j += numAxes; - } - - createShape(); - } - - /** - * Instantiate a shape based on an array containing coordinates. For best - * performance and stability, the convex hull should have no more than 100 - * vertices. - * - * @param points an array of coordinates on which to base the shape (not - * null, not empty, length a multiple of 3, unaffected) - */ - public HullCollisionShape(float... points) { - Validate.nonEmpty(points, "points"); - Validate.require( - points.length % numAxes == 0, "length a multiple of 3"); - - this.points = points.clone(); - createShape(); - } - - /** - * Instantiate a shape based on a flipped buffer containing coordinates. For - * best performance and stability, the convex hull should have no more than - * 100 vertices. - * - * @param flippedBuffer the coordinates on which to base the shape (not - * null, limit>0, limit a multiple of 3, unaffected) - */ - public HullCollisionShape(FloatBuffer flippedBuffer) { - Validate.nonNull(flippedBuffer, "flipped buffer"); - int numFloats = flippedBuffer.limit(); - Validate.positive(numFloats, "limit"); - Validate.require(numFloats % numAxes == 0, "limit a multiple of 3"); - - this.points = new float[numFloats]; - for (int i = 0; i < numFloats; ++i) { - this.points[i] = flippedBuffer.get(i); - } - - createShape(); - } - - /** - * Instantiate a shape based on the specified JME mesh(es). For best - * performance and stability, the convex hull should have no more than 100 - * vertices. - * - * @param meshes the mesh(es) on which to base the shape (all non-null, at - * least one vertex, unaffected) - */ - public HullCollisionShape(Mesh... meshes) { - Validate.nonEmpty(meshes, "meshes"); - this.points = getPoints(meshes); - Validate.require(points.length > 0, "at least one vertex"); - - createShape(); - } - - /** - * Instantiate an 8-vertex shape to match the specified rectangular solid. - * - * @param rectangularSolid the solid on which to base the shape (not null) - */ - public HullCollisionShape(RectangularSolid rectangularSolid) { - Vector3f maxima = rectangularSolid.maxima(null); - Vector3f minima = rectangularSolid.minima(null); - - // Enumerate the local coordinates of the 8 corners of the box. - Collection cornerLocations = new ArrayList<>(8); - cornerLocations.add(new Vector3f(maxima.x, maxima.y, maxima.z)); - cornerLocations.add(new Vector3f(maxima.x, maxima.y, minima.z)); - cornerLocations.add(new Vector3f(maxima.x, minima.y, maxima.z)); - cornerLocations.add(new Vector3f(maxima.x, minima.y, minima.z)); - cornerLocations.add(new Vector3f(minima.x, maxima.y, maxima.z)); - cornerLocations.add(new Vector3f(minima.x, maxima.y, minima.z)); - cornerLocations.add(new Vector3f(minima.x, minima.y, maxima.z)); - cornerLocations.add(new Vector3f(minima.x, minima.y, minima.z)); - - // Transform corner locations to shape coordinates. - int numFloats = numAxes * cornerLocations.size(); - this.points = new float[numFloats]; - int floatIndex = 0; - Vector3f tempVector = new Vector3f(); - for (Vector3f location : cornerLocations) { - rectangularSolid.localToWorld(location, tempVector); - this.points[floatIndex + PhysicsSpace.AXIS_X] = tempVector.x; - this.points[floatIndex + PhysicsSpace.AXIS_Y] = tempVector.y; - this.points[floatIndex + PhysicsSpace.AXIS_Z] = tempVector.z; - floatIndex += numAxes; - } - - createShape(); - } - - /** - * Instantiate a shape based on an array of locations. For best performance - * and stability, the convex hull should have no more than 100 vertices. - * - * @param locations an array of location vectors (in shape coordinates, not - * null, not empty, unaffected) - */ - public HullCollisionShape(Vector3f... locations) { - Validate.nonEmpty(locations, "points"); - - int numFloats = numAxes * locations.length; - this.points = new float[numFloats]; - int floatIndex = 0; - for (Vector3f location : locations) { - this.points[floatIndex + PhysicsSpace.AXIS_X] = location.x; - this.points[floatIndex + PhysicsSpace.AXIS_Y] = location.y; - this.points[floatIndex + PhysicsSpace.AXIS_Z] = location.z; - floatIndex += numAxes; - } - - createShape(); - } - - /** - * Instantiate a shape based on a Vhacd4Hull. For best performance and - * stability, the convex hull should have no more than 100 vertices. - * - * @param vhacd4Hull (not null, unaffected) - */ - public HullCollisionShape(Vhacd4Hull vhacd4Hull) { - Validate.nonNull(vhacd4Hull, "V-HACD hull"); - - this.points = vhacd4Hull.clonePositions(); - createShape(); - } - - /** - * Instantiate a shape based on a VHACDHull. For best performance and - * stability, the convex hull should have no more than 100 vertices. - * - * @param vhacdHull (not null, unaffected) - */ - public HullCollisionShape(VHACDHull vhacdHull) { - Validate.nonNull(vhacdHull, "V-HACD hull"); - - this.points = vhacdHull.clonePositions(); - createShape(); - } - // ************************************************************************* - // new methods exposed - - /** - * Calculate a quick upper bound for the unscaled volume of the hull, based - * on its axis-aligned bounding box. - * - * @return the volume (in unscaled shape units cubed, ≥0) - */ - public float aabbVolume() { - Vector3f maxima = new Vector3f(Float.NEGATIVE_INFINITY, - Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - Vector3f minima = new Vector3f(Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - - Vector3f location = new Vector3f(); - for (int floatI = 0; floatI < points.length; floatI += numAxes) { - float x = points[floatI + PhysicsSpace.AXIS_X]; - float y = points[floatI + PhysicsSpace.AXIS_Y]; - float z = points[floatI + PhysicsSpace.AXIS_Z]; - location.set(x, y, z); - MyVector3f.accumulateMinima(minima, location); - MyVector3f.accumulateMaxima(maxima, location); - } - - float dx = maxima.x - minima.x; - float dy = maxima.y - minima.y; - float dz = maxima.z - minima.z; - float volume = dx * dy * dz; - - assert volume >= 0f : volume; - assert Float.isFinite(volume) : volume; - return volume; - } - - /** - * Copy the unscaled vertex locations of the optimized convex hull. - * - * @return a new array (not null) - */ - public float[] copyHullVertices() { - long shapeId = nativeId(); - int numHullVertices = countHullVertices(); - FloatBuffer buffer - = BufferUtils.createFloatBuffer(numHullVertices * numAxes); - getHullVerticesF(shapeId, buffer); - - float[] result = new float[numHullVertices * numAxes]; - for (int floatI = 0; floatI < numHullVertices * numAxes; ++floatI) { - result[floatI] = buffer.get(floatI); - } - - return result; - } - - /** - * Count the number of vertices in the optimized convex hull. - * - * @return the count (≥0) - */ - public int countHullVertices() { - long shapeId = nativeId(); - int result = countHullVertices(shapeId); - - return result; - } - - /** - * Count the vertices used to generate the hull. - * - * @return the count (>0) - */ - public int countMeshVertices() { - int length = points.length; - assert (length % numAxes == 0) : length; - int result = length / numAxes; - - assert result > 0 : result; - return result; - } - - /** - * Calculate the unscaled half extents of the hull. - * - * @param storeResult storage for the result (modified if not null) - * @return the unscaled half extent for each local axis (either storeResult - * or a new vector, not null, no negative component) - */ - public Vector3f getHalfExtents(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - - result.zero(); - for (int i = 0; i < points.length; i += numAxes) { - float x = FastMath.abs(points[i + PhysicsSpace.AXIS_X]); - if (x > result.x) { - result.x = x; - } - float y = FastMath.abs(points[i + PhysicsSpace.AXIS_Y]); - if (y > result.y) { - result.y = y; - } - float z = FastMath.abs(points[i + PhysicsSpace.AXIS_Z]); - if (z > result.z) { - result.z = z; - } - } - - assert MyVector3f.isAllNonNegative(result) : result; - return result; - } - - /** - * Attempt to divide this shape into 2 child shapes. - * - * @param splittingTriangle to define the splitting plane (in shape - * coordinates, not null, unaffected) - * @return a pair of hull-based children, the first child generated by the - * plane's minus side and the 2nd child generated by its plus side; either - * child may be null, indicating an empty shape - */ - public ChildCollisionShape[] split(Triangle splittingTriangle) { - Validate.nonNull(splittingTriangle, "splitting triangle"); - - int numVertices = countHullVertices(); - int numFloats = numAxes * numVertices; - FloatBuffer originalHull = BufferUtils.createFloatBuffer(numFloats); - long shapeId = nativeId(); - getHullVerticesF(shapeId, originalHull); - - Vector3f normal = splittingTriangle.getNormal(); // alias - Plane splittingPlane = new Plane(normal, splittingTriangle.get3()); - /* - * Organize the hull vertices into 2 sets based on - * which side of the splitting plane they are on. - */ - VectorSet minusSet = new VectorSetUsingBuffer(numVertices, true); - VectorSet plusSet = new VectorSetUsingBuffer(numVertices, true); - Vector3f tmpVertex = new Vector3f(); - for (int vertexI = 0; vertexI < numVertices; ++vertexI) { - int startPosition = numAxes * vertexI; - MyBuffer.get(originalHull, startPosition, tmpVertex); - float pseudoDistance = splittingPlane.pseudoDistance(tmpVertex); - if (pseudoDistance <= 0f) { - minusSet.add(tmpVertex); - } - if (pseudoDistance >= 0f) { - plusSet.add(tmpVertex); - } - // Note: vertices that lie in the plane will appear in both sets. - } - - ChildCollisionShape[] result = new ChildCollisionShape[2]; - int numMinus = minusSet.numVectors(); - int numPlus = plusSet.numVectors(); - if (numMinus == 0 || numPlus == 0) { - // Degenerate case: all vertices lie to one side of the plane. - ChildCollisionShape child - = new ChildCollisionShape(new Vector3f(), this); - if (numMinus > 0) { - result[0] = child; - } else if (numPlus > 0) { - result[1] = child; - } - return result; - } - - // Copy all minus-side vertices to a new set. - FloatBuffer minusBuffer = minusSet.toBuffer(); - VectorSet newMinusSet = new VectorSetUsingBuffer(numVertices, true); - for (int jNegative = 0; jNegative < numMinus; ++jNegative) { - MyBuffer.get(minusBuffer, numAxes * jNegative, tmpVertex); - newMinusSet.add(tmpVertex); - } - /* - * Copy all plus-side vertices to a new set. - * Also: interpolate each of the original plus-side vertices - * with each of the original minus-side vertices - * and add the interpolated locations to both of the new sets. - */ - FloatBuffer plusBuffer = plusSet.toBuffer(); - VectorSet newPlusSet = new VectorSetUsingBuffer(numVertices, true); - Vector3f tmp2 = new Vector3f(); - for (int plusI = 0; plusI < numPlus; ++plusI) { - MyBuffer.get(plusBuffer, numAxes * plusI, tmpVertex); - newPlusSet.add(tmpVertex); - float pd = splittingPlane.pseudoDistance(tmpVertex); - - for (int minusI = 0; minusI < numMinus; ++minusI) { - MyBuffer.get(minusBuffer, numAxes * minusI, tmp2); - float md = splittingPlane.pseudoDistance(tmp2); - float denominator = pd - md; - if (denominator != 0f) { - float t = -md / denominator; - MyVector3f.lerp(t, tmp2, tmpVertex, tmp2); - newMinusSet.add(tmp2); - newPlusSet.add(tmp2); - } - } - } - /* - * Translate minus-side vertices so their AABB is centered at (0,0,0) - * and use them to form a new hull shape. - */ - Vector3f max = tmpVertex; // alias - Vector3f min = tmp2; // alias - Vector3f offset = tmpVertex; // alias - newMinusSet.maxMin(max, min); - Vector3f minusCenter = MyVector3f.midpoint(max, min, null); - offset.set(minusCenter).negateLocal(); - FloatBuffer flippedBuffer = newMinusSet.toBuffer(); - MyBuffer.translate(flippedBuffer, 0, flippedBuffer.limit(), offset); - HullCollisionShape minusShape = new HullCollisionShape(flippedBuffer); - minusShape.setScale(scale); - result[0] = new ChildCollisionShape(minusCenter, minusShape); - /* - * Translate plus-side vertices so their AABB is centered at (0,0,0) - * and use them to form a new hull shape. - */ - newPlusSet.maxMin(max, min); - Vector3f plusCenter = MyVector3f.midpoint(max, min, null); - offset.set(plusCenter).negateLocal(); - flippedBuffer = newPlusSet.toBuffer(); - MyBuffer.translate(flippedBuffer, 0, flippedBuffer.limit(), offset); - HullCollisionShape plusShape = new HullCollisionShape(flippedBuffer); - plusShape.setScale(scale); - result[1] = new ChildCollisionShape(plusCenter, plusShape); - - return result; - } - // ************************************************************************* - // ConvexShape methods - - /** - * Test whether this shape can be split by an arbitrary plane. - * - * @return true if splittable, false otherwise - */ - @Override - public boolean canSplit() { - return true; - } - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned shape into a deep-cloned one, using the specified Cloner - * and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this shape (not null) - * @param original the instance from which this shape was shallow-cloned - * (not null, unaffected) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - super.cloneFields(cloner, original); - this.directBuffer = null; // directBuffer is never cloned. - this.points = cloner.clone(points); - createShape(); - } - - /** - * Calculate how far this shape extends from its center, including margin. - * - * @return a distance (in physics-space units, ≥0) - */ - @Override - public float maxRadius() { - int numHullVertices = countHullVertices(); - FloatBuffer buffer - = BufferUtils.createFloatBuffer(numHullVertices * numAxes); - long shapeId = nativeId(); - getHullVerticesF(shapeId, buffer); - double maxSquaredDistance = 0.0; - - for (int vertexI = 0; vertexI < numHullVertices; ++vertexI) { - int startOffset = numAxes * vertexI; - float x = scale.x * buffer.get(startOffset + PhysicsSpace.AXIS_X); - float y = scale.y * buffer.get(startOffset + PhysicsSpace.AXIS_Y); - float z = scale.z * buffer.get(startOffset + PhysicsSpace.AXIS_Z); - double lengthSquared = MyMath.sumOfSquares(x, y, z); - if (lengthSquared > maxSquaredDistance) { - maxSquaredDistance = lengthSquared; - } - } - float result = margin + (float) Math.sqrt(maxSquaredDistance); - - return result; - } - - /** - * De-serialize this shape from the specified importer, for example when - * loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - super.read(importer); - InputCapsule capsule = importer.getCapsule(this); - - // for backwards compatibility - Mesh mesh = (Mesh) capsule.readSavable(tagHullMesh, null); - if (mesh != null) { - this.points = getPoints(mesh); - } else { - this.points = capsule.readFloatArray(tagPoints, new float[0]); - } - createShape(); - } - - /** - * Recalculate this shape's bounding box if necessary. - */ - @Override - protected void recalculateAabb() { - long shapeId = nativeId(); - recalcAabb(shapeId); - } - - /** - * Serialize this shape to the specified exporter, for example when saving - * to a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - super.write(exporter); - OutputCapsule capsule = exporter.getCapsule(this); - - float[] vertices = copyHullVertices(); - capsule.write(vertices, tagPoints, new float[0]); - } - // ************************************************************************* - // Java private methods - - /** - * Instantiate the configured shape in Bullet. - */ - private void createShape() { - assert directBuffer == null : directBuffer; - - int numFloats = points.length; - assert numFloats != 0; - assert (numFloats % numAxes == 0) : numFloats; - int numVertices = numFloats / numAxes; - - this.directBuffer = BufferUtils.createFloatBuffer(numFloats); - for (float f : points) { - if (!Float.isFinite(f)) { - throw new IllegalArgumentException("illegal coordinate: " + f); - } - directBuffer.put(f); - } - - long shapeId = createShapeF(directBuffer, numVertices); - setNativeId(shapeId); - - setContactFilterEnabled(enableContactFilter); - setScale(scale); - setMargin(margin); - } - - /** - * Copy the vertex positions from JME mesh(es). - * - * @param meshes the mesh(es) to read (not null) - * @return a new array (not null, length a multiple of 3) - */ - private static float[] getPoints(Mesh... meshes) { - int numVectors = 0; - for (Mesh mesh : meshes) { - numVectors += mesh.getVertexCount(); - } - int numFloats = numAxes * numVectors; - float[] pointsArray = new float[numFloats]; - - int arrayIndex = 0; - for (Mesh mesh : meshes) { - FloatBuffer buffer = mesh.getFloatBuffer(Type.Position); - int bufNumFloats = numAxes * mesh.getVertexCount(); - for (int bufPos = 0; bufPos < bufNumFloats; ++bufPos) { - pointsArray[arrayIndex] = buffer.get(bufPos); - ++arrayIndex; - } - } - assert arrayIndex == numFloats : arrayIndex; - - return pointsArray; - } - // ************************************************************************* - // native private methods - - native private static int countHullVertices(long shapeId); - - native private static long - createShapeF(FloatBuffer vertices, int numVertices); - - native private static void - getHullVerticesF(long shapeId, FloatBuffer vertices); - - native private static void recalcAabb(long shapeId); -} +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Triangle; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.clone.Cloner; +import java.io.IOException; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.logging.Logger; +import jme3utilities.Validate; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyMath; +import jme3utilities.math.MyVector3f; +import jme3utilities.math.RectangularSolid; +import jme3utilities.math.VectorSet; +import jme3utilities.math.VectorSetUsingBuffer; +import vhacd.VHACDHull; +import vhacd4.Vhacd4Hull; + +/** + * A convex-hull collision shape based on Bullet's {@code btConvexHullShape}. + * For a 2-D convex hull, use Convex2dShape. + */ +public class HullCollisionShape extends ConvexShape { + // ************************************************************************* + // constants and loggers + + /** + * number of axes in a vector + */ + final private static int numAxes = 3; + /** + * message logger for this class + */ + final public static Logger logger2 + = Logger.getLogger(HullCollisionShape.class.getName()); + /** + * field names for serialization + */ + final private static String tagHullMesh = "hullMesh"; + final private static String tagPoints = "points"; + // ************************************************************************* + // fields + + /** + * direct buffer for passing vertices to Bullet + *

+ * A Java reference must persist after createShape() completes, or else the + * buffer might get garbage collected. + */ + private FloatBuffer directBuffer; + /** + * array of mesh coordinates (not null, not empty, length a multiple of 3) + */ + private float[] points; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected HullCollisionShape() { + } + + /** + * Instantiate a shape based on the specified collection of locations. For + * best performance and stability, the convex hull should have no more than + * 100 vertices. + * + * @param locations a collection of location vectors on which to base the + * shape (not null, not empty, unaffected) + */ + public HullCollisionShape(Collection locations) { + Validate.nonEmpty(locations, "locations"); + + int numLocations = locations.size(); + this.points = new float[numAxes * numLocations]; + int j = 0; + for (Vector3f location : locations) { + this.points[j + PhysicsSpace.AXIS_X] = location.x; + this.points[j + PhysicsSpace.AXIS_Y] = location.y; + this.points[j + PhysicsSpace.AXIS_Z] = location.z; + j += numAxes; + } + + createShape(); + } + + /** + * Instantiate a shape based on an array containing coordinates. For best + * performance and stability, the convex hull should have no more than 100 + * vertices. + * + * @param points an array of coordinates on which to base the shape (not + * null, not empty, length a multiple of 3, unaffected) + */ + public HullCollisionShape(float... points) { + Validate.nonEmpty(points, "points"); + Validate.require( + points.length % numAxes == 0, "length a multiple of 3"); + + this.points = points.clone(); + createShape(); + } + + /** + * Instantiate a shape based on a flipped buffer containing coordinates. For + * best performance and stability, the convex hull should have no more than + * 100 vertices. + * + * @param flippedBuffer the coordinates on which to base the shape (not + * null, limit>0, limit a multiple of 3, unaffected) + */ + public HullCollisionShape(FloatBuffer flippedBuffer) { + Validate.nonNull(flippedBuffer, "flipped buffer"); + int numFloats = flippedBuffer.limit(); + Validate.positive(numFloats, "limit"); + Validate.require(numFloats % numAxes == 0, "limit a multiple of 3"); + + this.points = new float[numFloats]; + for (int i = 0; i < numFloats; ++i) { + this.points[i] = flippedBuffer.get(i); + } + + createShape(); + } + + /** + * Instantiate a shape based on the specified JME mesh(es). For best + * performance and stability, the convex hull should have no more than 100 + * vertices. + * + * @param meshes the mesh(es) on which to base the shape (all non-null, at + * least one vertex, unaffected) + */ + public HullCollisionShape(Mesh... meshes) { + Validate.nonEmpty(meshes, "meshes"); + this.points = getPoints(meshes); + Validate.require(points.length > 0, "at least one vertex"); + + createShape(); + } + + /** + * Instantiate an 8-vertex shape to match the specified rectangular solid. + * + * @param rectangularSolid the solid on which to base the shape (not null) + */ + public HullCollisionShape(RectangularSolid rectangularSolid) { + Vector3f maxima = rectangularSolid.maxima(null); + Vector3f minima = rectangularSolid.minima(null); + + // Enumerate the local coordinates of the 8 corners of the box. + Collection cornerLocations = new ArrayList<>(8); + cornerLocations.add(new Vector3f(maxima.x, maxima.y, maxima.z)); + cornerLocations.add(new Vector3f(maxima.x, maxima.y, minima.z)); + cornerLocations.add(new Vector3f(maxima.x, minima.y, maxima.z)); + cornerLocations.add(new Vector3f(maxima.x, minima.y, minima.z)); + cornerLocations.add(new Vector3f(minima.x, maxima.y, maxima.z)); + cornerLocations.add(new Vector3f(minima.x, maxima.y, minima.z)); + cornerLocations.add(new Vector3f(minima.x, minima.y, maxima.z)); + cornerLocations.add(new Vector3f(minima.x, minima.y, minima.z)); + + // Transform corner locations to shape coordinates. + int numFloats = numAxes * cornerLocations.size(); + this.points = new float[numFloats]; + int floatIndex = 0; + Vector3f tempVector = new Vector3f(); + for (Vector3f location : cornerLocations) { + rectangularSolid.localToWorld(location, tempVector); + this.points[floatIndex + PhysicsSpace.AXIS_X] = tempVector.x; + this.points[floatIndex + PhysicsSpace.AXIS_Y] = tempVector.y; + this.points[floatIndex + PhysicsSpace.AXIS_Z] = tempVector.z; + floatIndex += numAxes; + } + + createShape(); + } + + /** + * Instantiate a shape based on an array of locations. For best performance + * and stability, the convex hull should have no more than 100 vertices. + * + * @param locations an array of location vectors (in shape coordinates, not + * null, not empty, unaffected) + */ + public HullCollisionShape(Vector3f... locations) { + Validate.nonEmpty(locations, "points"); + + int numFloats = numAxes * locations.length; + this.points = new float[numFloats]; + int floatIndex = 0; + for (Vector3f location : locations) { + this.points[floatIndex + PhysicsSpace.AXIS_X] = location.x; + this.points[floatIndex + PhysicsSpace.AXIS_Y] = location.y; + this.points[floatIndex + PhysicsSpace.AXIS_Z] = location.z; + floatIndex += numAxes; + } + + createShape(); + } + + /** + * Instantiate a shape based on a Vhacd4Hull. For best performance and + * stability, the convex hull should have no more than 100 vertices. + * + * @param vhacd4Hull (not null, unaffected) + */ + public HullCollisionShape(Vhacd4Hull vhacd4Hull) { + Validate.nonNull(vhacd4Hull, "V-HACD hull"); + + this.points = vhacd4Hull.clonePositions(); + createShape(); + } + + /** + * Instantiate a shape based on a VHACDHull. For best performance and + * stability, the convex hull should have no more than 100 vertices. + * + * @param vhacdHull (not null, unaffected) + */ + public HullCollisionShape(VHACDHull vhacdHull) { + Validate.nonNull(vhacdHull, "V-HACD hull"); + + this.points = vhacdHull.clonePositions(); + createShape(); + } + // ************************************************************************* + // new methods exposed + + /** + * Calculate a quick upper bound for the unscaled volume of the hull, based + * on its axis-aligned bounding box. + * + * @return the volume (in unscaled shape units cubed, ≥0) + */ + public float aabbVolume() { + Vector3f maxima = new Vector3f(Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + Vector3f minima = new Vector3f(Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + + Vector3f location = new Vector3f(); + for (int floatI = 0; floatI < points.length; floatI += numAxes) { + float x = points[floatI + PhysicsSpace.AXIS_X]; + float y = points[floatI + PhysicsSpace.AXIS_Y]; + float z = points[floatI + PhysicsSpace.AXIS_Z]; + location.set(x, y, z); + MyVector3f.accumulateMinima(minima, location); + MyVector3f.accumulateMaxima(maxima, location); + } + + float dx = maxima.x - minima.x; + float dy = maxima.y - minima.y; + float dz = maxima.z - minima.z; + float volume = dx * dy * dz; + + assert volume >= 0f : volume; + assert Float.isFinite(volume) : volume; + return volume; + } + + /** + * Copy the unscaled vertex locations of the optimized convex hull. + * + * @return a new array (not null) + */ + public float[] copyHullVertices() { + long shapeId = nativeId(); + int numHullVertices = countHullVertices(); + FloatBuffer buffer + = BufferUtils.createFloatBuffer(numHullVertices * numAxes); + getHullVerticesF(shapeId, buffer); + + float[] result = new float[numHullVertices * numAxes]; + for (int floatI = 0; floatI < numHullVertices * numAxes; ++floatI) { + result[floatI] = buffer.get(floatI); + } + + return result; + } + + /** + * Count the number of vertices in the optimized convex hull. + * + * @return the count (≥0) + */ + public int countHullVertices() { + long shapeId = nativeId(); + int result = countHullVertices(shapeId); + + return result; + } + + /** + * Count the vertices used to generate the hull. + * + * @return the count (>0) + */ + public int countMeshVertices() { + int length = points.length; + assert (length % numAxes == 0) : length; + int result = length / numAxes; + + assert result > 0 : result; + return result; + } + + /** + * Calculate the unscaled half extents of the hull. + * + * @param storeResult storage for the result (modified if not null) + * @return the unscaled half extent for each local axis (either storeResult + * or a new vector, not null, no negative component) + */ + public Vector3f getHalfExtents(Vector3f storeResult) { + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + + result.zero(); + for (int i = 0; i < points.length; i += numAxes) { + float x = FastMath.abs(points[i + PhysicsSpace.AXIS_X]); + if (x > result.x) { + result.x = x; + } + float y = FastMath.abs(points[i + PhysicsSpace.AXIS_Y]); + if (y > result.y) { + result.y = y; + } + float z = FastMath.abs(points[i + PhysicsSpace.AXIS_Z]); + if (z > result.z) { + result.z = z; + } + } + + assert MyVector3f.isAllNonNegative(result) : result; + return result; + } + + /** + * Attempt to divide this shape into 2 child shapes. + * + * @param splittingTriangle to define the splitting plane (in shape + * coordinates, not null, unaffected) + * @return a pair of hull-based children, the first child generated by the + * plane's minus side and the 2nd child generated by its plus side; either + * child may be null, indicating an empty shape + */ + public ChildCollisionShape[] split(Triangle splittingTriangle) { + Validate.nonNull(splittingTriangle, "splitting triangle"); + + int numVertices = countHullVertices(); + int numFloats = numAxes * numVertices; + FloatBuffer originalHull = BufferUtils.createFloatBuffer(numFloats); + long shapeId = nativeId(); + getHullVerticesF(shapeId, originalHull); + + Vector3f normal = splittingTriangle.getNormal(); // alias + Plane splittingPlane = new Plane(normal, splittingTriangle.get3()); + /* + * Organize the hull vertices into 2 sets based on + * which side of the splitting plane they are on. + */ + VectorSet minusSet = new VectorSetUsingBuffer(numVertices, true); + VectorSet plusSet = new VectorSetUsingBuffer(numVertices, true); + Vector3f tmpVertex = new Vector3f(); + for (int vertexI = 0; vertexI < numVertices; ++vertexI) { + int startPosition = numAxes * vertexI; + MyBuffer.get(originalHull, startPosition, tmpVertex); + float pseudoDistance = splittingPlane.pseudoDistance(tmpVertex); + if (pseudoDistance <= 0f) { + minusSet.add(tmpVertex); + } + if (pseudoDistance >= 0f) { + plusSet.add(tmpVertex); + } + // Note: vertices that lie in the plane will appear in both sets. + } + + ChildCollisionShape[] result = new ChildCollisionShape[2]; + int numMinus = minusSet.numVectors(); + int numPlus = plusSet.numVectors(); + if (numMinus == 0 || numPlus == 0) { + // Degenerate case: all vertices lie to one side of the plane. + ChildCollisionShape child + = new ChildCollisionShape(new Vector3f(), this); + if (numMinus > 0) { + result[0] = child; + } else if (numPlus > 0) { + result[1] = child; + } + return result; + } + + // Copy all minus-side vertices to a new set. + FloatBuffer minusBuffer = minusSet.toBuffer(); + VectorSet newMinusSet = new VectorSetUsingBuffer(numVertices, true); + for (int jNegative = 0; jNegative < numMinus; ++jNegative) { + MyBuffer.get(minusBuffer, numAxes * jNegative, tmpVertex); + newMinusSet.add(tmpVertex); + } + /* + * Copy all plus-side vertices to a new set. + * Also: interpolate each of the original plus-side vertices + * with each of the original minus-side vertices + * and add the interpolated locations to both of the new sets. + */ + FloatBuffer plusBuffer = plusSet.toBuffer(); + VectorSet newPlusSet = new VectorSetUsingBuffer(numVertices, true); + Vector3f tmp2 = new Vector3f(); + for (int plusI = 0; plusI < numPlus; ++plusI) { + MyBuffer.get(plusBuffer, numAxes * plusI, tmpVertex); + newPlusSet.add(tmpVertex); + float pd = splittingPlane.pseudoDistance(tmpVertex); + + for (int minusI = 0; minusI < numMinus; ++minusI) { + MyBuffer.get(minusBuffer, numAxes * minusI, tmp2); + float md = splittingPlane.pseudoDistance(tmp2); + float denominator = pd - md; + if (denominator != 0f) { + float t = -md / denominator; + MyVector3f.lerp(t, tmp2, tmpVertex, tmp2); + newMinusSet.add(tmp2); + newPlusSet.add(tmp2); + } + } + } + /* + * Translate minus-side vertices so their AABB is centered at (0,0,0) + * and use them to form a new hull shape. + */ + Vector3f max = tmpVertex; // alias + Vector3f min = tmp2; // alias + Vector3f offset = tmpVertex; // alias + newMinusSet.maxMin(max, min); + Vector3f minusCenter = MyVector3f.midpoint(max, min, null); + offset.set(minusCenter).negateLocal(); + FloatBuffer flippedBuffer = newMinusSet.toBuffer(); + MyBuffer.translate(flippedBuffer, 0, flippedBuffer.limit(), offset); + HullCollisionShape minusShape = new HullCollisionShape(flippedBuffer); + minusShape.setScale(scale); + result[0] = new ChildCollisionShape(minusCenter, minusShape); + /* + * Translate plus-side vertices so their AABB is centered at (0,0,0) + * and use them to form a new hull shape. + */ + newPlusSet.maxMin(max, min); + Vector3f plusCenter = MyVector3f.midpoint(max, min, null); + offset.set(plusCenter).negateLocal(); + flippedBuffer = newPlusSet.toBuffer(); + MyBuffer.translate(flippedBuffer, 0, flippedBuffer.limit(), offset); + HullCollisionShape plusShape = new HullCollisionShape(flippedBuffer); + plusShape.setScale(scale); + result[1] = new ChildCollisionShape(plusCenter, plusShape); + + return result; + } + // ************************************************************************* + // ConvexShape methods + + /** + * Test whether this shape can be split by an arbitrary plane. + * + * @return true if splittable, false otherwise + */ + @Override + public boolean canSplit() { + return true; + } + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned shape into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this shape (not null) + * @param original the instance from which this shape was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + this.directBuffer = null; // directBuffer is never cloned. + this.points = cloner.clone(points); + createShape(); + } + + /** + * Calculate how far this shape extends from its center, including margin. + * + * @return a distance (in physics-space units, ≥0) + */ + @Override + public float maxRadius() { + int numHullVertices = countHullVertices(); + FloatBuffer buffer + = BufferUtils.createFloatBuffer(numHullVertices * numAxes); + long shapeId = nativeId(); + getHullVerticesF(shapeId, buffer); + double maxSquaredDistance = 0.0; + + for (int vertexI = 0; vertexI < numHullVertices; ++vertexI) { + int startOffset = numAxes * vertexI; + float x = scale.x * buffer.get(startOffset + PhysicsSpace.AXIS_X); + float y = scale.y * buffer.get(startOffset + PhysicsSpace.AXIS_Y); + float z = scale.z * buffer.get(startOffset + PhysicsSpace.AXIS_Z); + double lengthSquared = MyMath.sumOfSquares(x, y, z); + if (lengthSquared > maxSquaredDistance) { + maxSquaredDistance = lengthSquared; + } + } + float result = margin + (float) Math.sqrt(maxSquaredDistance); + + return result; + } + + /** + * De-serialize this shape from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + // for backwards compatibility + Mesh mesh = (Mesh) capsule.readSavable(tagHullMesh, null); + if (mesh != null) { + this.points = getPoints(mesh); + } else { + this.points = capsule.readFloatArray(tagPoints, new float[0]); + } + createShape(); + } + + /** + * Recalculate this shape's bounding box if necessary. + */ + @Override + protected void recalculateAabb() { + long shapeId = nativeId(); + recalcAabb(shapeId); + } + + /** + * Serialize this shape to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + float[] vertices = copyHullVertices(); + capsule.write(vertices, tagPoints, new float[0]); + } + // ************************************************************************* + // Java private methods + + /** + * Instantiate the configured shape in Bullet. + */ + private void createShape() { + assert directBuffer == null : directBuffer; + + int numFloats = points.length; + assert numFloats != 0; + assert (numFloats % numAxes == 0) : numFloats; + int numVertices = numFloats / numAxes; + + this.directBuffer = BufferUtils.createFloatBuffer(numFloats); + for (float f : points) { + if (!Float.isFinite(f)) { + throw new IllegalArgumentException("illegal coordinate: " + f); + } + directBuffer.put(f); + } + + long shapeId = createShapeF(directBuffer, numVertices); + setNativeId(shapeId); + + setContactFilterEnabled(enableContactFilter); + setScale(scale); + setMargin(margin); + } + + /** + * Copy the vertex positions from JME mesh(es). + * + * @param meshes the mesh(es) to read (not null) + * @return a new array (not null, length a multiple of 3) + */ + private static float[] getPoints(Mesh... meshes) { + int numVectors = 0; + for (Mesh mesh : meshes) { + numVectors += mesh.getVertexCount(); + } + int numFloats = numAxes * numVectors; + float[] pointsArray = new float[numFloats]; + + int arrayIndex = 0; + for (Mesh mesh : meshes) { + FloatBuffer buffer = mesh.getFloatBuffer(Type.Position); + int bufNumFloats = numAxes * mesh.getVertexCount(); + for (int bufPos = 0; bufPos < bufNumFloats; ++bufPos) { + pointsArray[arrayIndex] = buffer.get(bufPos); + ++arrayIndex; + } + } + assert arrayIndex == numFloats : arrayIndex; + + return pointsArray; + } + // ************************************************************************* + // native private methods + + native private static int countHullVertices(long shapeId); + + native private static long + createShapeF(FloatBuffer vertices, int numVertices); + + native private static void + getHullVerticesF(long shapeId, FloatBuffer vertices); + + native private static void recalcAabb(long shapeId); +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java index 653ef60c9..dc6f26d5e 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java @@ -1,404 +1,404 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.collision.shapes; - -import com.jme3.bullet.collision.shapes.infos.BoundingValueHierarchy; -import com.jme3.bullet.collision.shapes.infos.CompoundMesh; -import com.jme3.bullet.collision.shapes.infos.IndexedMesh; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Triangle; -import com.jme3.scene.Mesh; -import com.jme3.system.JmeSystem; -import com.jme3.system.Platform; -import com.jme3.util.clone.Cloner; -import java.io.IOException; -import java.util.Collection; -import java.util.logging.Logger; -import jme3utilities.Validate; - -/** - * A mesh collision shape that uses a Bounding Value Hierarchy (BVH), based on - * Bullet's {@code btBvhTriangleMeshShape}. Not for use in dynamic bodies. - * Collisions between HeightfieldCollisionShape, MeshCollisionShape, and - * PlaneCollisionShape objects are never detected. - *

- * TODO add a shape based on {@code btScaledBvhTriangleMeshShape} - * - * @author normenhansen - */ -public class MeshCollisionShape extends CollisionShape { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger2 - = Logger.getLogger(MeshCollisionShape.class.getName()); - /** - * field names for serialization - */ - final private static String tagBvh = "bvh"; - final private static String tagNativePlatform = "nativePlatform"; - final private static String tagNativeMesh = "nativeMesh"; - final private static String tagUseCompression = "useCompression"; - // ************************************************************************* - // fields - - /** - * if true, use quantized AABB compression (default=true) - */ - private boolean useCompression; - /** - * bounding-value hierarchy - */ - private BoundingValueHierarchy bvh; - /** - * native mesh used to construct this shape - */ - private CompoundMesh nativeMesh; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected MeshCollisionShape() { - } - - /** - * Instantiate a shape from the specified collection of native meshes. - * - * @param useCompression true to use quantized AABB compression - * @param meshes the collection on which to base the shape (must contain at - * least one triangle) - */ - public MeshCollisionShape( - boolean useCompression, Collection meshes) { - Validate.nonEmpty(meshes, "meshes"); - this.nativeMesh = new CompoundMesh(); - for (IndexedMesh submesh : meshes) { - nativeMesh.add(submesh); - } - Validate.require( - nativeMesh.countTriangles() > 0, "at least one triangle"); - - this.useCompression = useCompression; - createShape(); - } - - /** - * Instantiate a shape based on the specified CompoundMesh. - * - * @param useCompression true to use quantized AABB compression - * @param mesh the mesh on which to base the shape (not null, must contain - * at least one triangle, unaffected) - */ - public MeshCollisionShape(boolean useCompression, CompoundMesh mesh) { - Validate.require(mesh.countTriangles() > 0, "at least one triangle"); - - this.nativeMesh = new CompoundMesh(mesh); - this.useCompression = useCompression; - createShape(); - } - - /** - * Instantiate a shape from the specified native mesh(es). - * - * @param useCompression true to use quantized AABB compression - * @param submeshes the mesh(es) on which to base the shape (must contain at - * least one triangle) - */ - public MeshCollisionShape( - boolean useCompression, IndexedMesh... submeshes) { - Validate.nonEmpty(submeshes, "submeshes"); - this.nativeMesh = new CompoundMesh(); - for (IndexedMesh submesh : submeshes) { - nativeMesh.add(submesh); - } - Validate.require( - nativeMesh.countTriangles() > 0, "at least one triangle"); - - this.useCompression = useCompression; - createShape(); - } - - /** - * Instantiate a shape from the specified native mesh(es) and serialized - * BVH. The submeshes must be equivalent to those used to generate the BVH. - * - * @param bvhBytes the serialized BVH (not null, unaffected) - * @param submeshes the mesh(es) on which to base the shape (must contain at - * least one triangle) - */ - public MeshCollisionShape(byte[] bvhBytes, IndexedMesh... submeshes) { - Validate.nonNull(bvhBytes, "BVH data"); - Validate.nonEmpty(submeshes, "submeshes"); - this.nativeMesh = new CompoundMesh(); - for (IndexedMesh submesh : submeshes) { - nativeMesh.add(submesh); - } - Validate.require( - nativeMesh.countTriangles() > 0, "at least one triangle"); - - this.useCompression = true; - this.bvh = new BoundingValueHierarchy(bvhBytes); - createShape(); - } - - /** - * Instantiate a shape based on the specified JME mesh(es), using quantized - * AABB compression. - * - * @param jmeMeshes the mesh(es) on which to base the shape (must contain at - * least one triangle, unaffected) - */ - public MeshCollisionShape(Mesh... jmeMeshes) { - Validate.nonEmpty(jmeMeshes, "JME meshes"); - this.nativeMesh = new CompoundMesh(jmeMeshes); - Validate.require(nativeMesh.countTriangles() > 0, - "at least one triangle"); - - this.useCompression = true; - createShape(); - } - - /** - * Instantiate a shape based on the specified JME mesh. - * - * @param mesh the mesh on which to base the shape (must contain at least - * one triangle, unaffected) - * @param useCompression true to use quantized AABB compression - */ - public MeshCollisionShape(Mesh mesh, boolean useCompression) { - Validate.nonNull(mesh, "mesh"); - this.nativeMesh = new CompoundMesh(mesh); - Validate.require(nativeMesh.countTriangles() > 0, - "at least one triangle"); - - this.useCompression = useCompression; - createShape(); - } - // ************************************************************************* - // new methods exposed - - /** - * Count how many triangles are in the mesh. - * - * @return the count (≥0) - */ - public int countMeshTriangles() { - int result = nativeMesh.countTriangles(); - return result; - } - - /** - * Count how many vertices are in the mesh. - * - * @return the count (≥0) - */ - public int countMeshVertices() { - int numVertices = nativeMesh.countVertices(); - return numVertices; - } - - /** - * Serialize the BVH to a byte array. - * - * @return a new array containing a serialized version of the BVH - */ - public byte[] serializeBvh() { - byte[] result = bvh.serialize(); - return result; - } - - /** - * Attempt to divide this shape into 2 shapes. - * - * @param splittingTriangle to define the splitting plane (in shape - * coordinates, not null, unaffected) - * @return a pair of mesh shapes, the first shape generated by the plane's - * minus side and the 2nd shape generated by its plus side; either shape may - * be null, indicating an empty shape - */ - public MeshCollisionShape[] split(Triangle splittingTriangle) { - Validate.nonNull(splittingTriangle, "splitting triangle"); - - CompoundMesh[] mp = nativeMesh.split(splittingTriangle); - MeshCollisionShape[] result = new MeshCollisionShape[2]; - int numMinus = (mp[0] == null) ? 0 : mp[0].countTriangles(); - int numPlus = (mp[1] == null) ? 0 : mp[1].countTriangles(); - if (numMinus == 0 || numPlus == 0) { - // Degenerate case: all triangles lie to one side of the plane. - if (numMinus > 0) { - result[0] = this; - } else if (numPlus > 0) { - result[1] = this; - } - - } else { - result[0] = new MeshCollisionShape(useCompression, mp[0]); - result[0].setScale(scale); - - result[1] = new MeshCollisionShape(useCompression, mp[1]); - result[1].setScale(scale); - } - - return result; - } - // ************************************************************************* - // CollisionShape methods - - /** - * Test whether this shape can be split by an arbitrary plane. - * - * @return true if splittable, false otherwise - */ - @Override - public boolean canSplit() { - return true; - } - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned shape into a deep-cloned one, using the specified Cloner - * and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this shape (not null) - * @param original the instance from which this shape was shallow-cloned - * (not null, unaffected) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - super.cloneFields(cloner, original); - - this.nativeMesh = cloner.clone(nativeMesh); - this.bvh = cloner.clone(bvh); - createShape(); - } - - /** - * De-serialize this shape from the specified importer, for example when - * loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - super.read(importer); - InputCapsule capsule = importer.getCapsule(this); - - Platform writePlatform - = capsule.readEnum(tagNativePlatform, Platform.class, null); - if (writePlatform == null || writePlatform != JmeSystem.getPlatform()) { - this.bvh = null; // will re-generate the BVH for the new platform - } else { - this.bvh = (BoundingValueHierarchy) capsule.readSavable( - tagBvh, null); - } - - this.nativeMesh - = (CompoundMesh) capsule.readSavable(tagNativeMesh, null); - this.useCompression = capsule.readBoolean(tagUseCompression, true); - - createShape(); - } - - /** - * Recalculate this shape's bounding box if necessary. - */ - @Override - protected void recalculateAabb() { - long shapeId = nativeId(); - recalcAabb(shapeId); - } - - /** - * Serialize this shape to the specified exporter, for example when saving - * to a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - super.write(exporter); - OutputCapsule capsule = exporter.getCapsule(this); - - capsule.write(bvh, tagBvh, null); - - Platform nativePlatform = JmeSystem.getPlatform(); - capsule.write(nativePlatform, tagNativePlatform, null); - - capsule.write(nativeMesh, tagNativeMesh, null); - capsule.write(useCompression, tagUseCompression, true); - } - // ************************************************************************* - // Java private methods - - /** - * Instantiate the configured {@code btBvhTriangleMeshShape}. - */ - private void createShape() { - int numTriangles = nativeMesh.countTriangles(); - assert numTriangles > 0 : numTriangles; - - boolean buildBvh = (bvh == null); - long meshId = nativeMesh.nativeId(); - long shapeId = createShape(useCompression, buildBvh, meshId); - setNativeId(shapeId); - - if (buildBvh) { - this.bvh = new BoundingValueHierarchy(this); - } else { - long bvhId = bvh.nativeId(); - setOptimizedBvh(shapeId, bvhId); - } - - setContactFilterEnabled(enableContactFilter); - setScale(scale); - setMargin(margin); - } - // ************************************************************************* - // native private methods - - native private static long - createShape(boolean useCompression, boolean buildBvh, long meshId); - - native private static void recalcAabb(long shapeId); - - native private static void setOptimizedBvh(long shapeId, long bvhId); -} +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes; + +import com.jme3.bullet.collision.shapes.infos.BoundingValueHierarchy; +import com.jme3.bullet.collision.shapes.infos.CompoundMesh; +import com.jme3.bullet.collision.shapes.infos.IndexedMesh; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Triangle; +import com.jme3.scene.Mesh; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import com.jme3.util.clone.Cloner; +import java.io.IOException; +import java.util.Collection; +import java.util.logging.Logger; +import jme3utilities.Validate; + +/** + * A mesh collision shape that uses a Bounding Value Hierarchy (BVH), based on + * Bullet's {@code btBvhTriangleMeshShape}. Not for use in dynamic bodies. + * Collisions between HeightfieldCollisionShape, MeshCollisionShape, and + * PlaneCollisionShape objects are never detected. + *

+ * TODO add a shape based on {@code btScaledBvhTriangleMeshShape} + * + * @author normenhansen + */ +public class MeshCollisionShape extends CollisionShape { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger2 + = Logger.getLogger(MeshCollisionShape.class.getName()); + /** + * field names for serialization + */ + final private static String tagBvh = "bvh"; + final private static String tagNativePlatform = "nativePlatform"; + final private static String tagNativeMesh = "nativeMesh"; + final private static String tagUseCompression = "useCompression"; + // ************************************************************************* + // fields + + /** + * if true, use quantized AABB compression (default=true) + */ + private boolean useCompression; + /** + * bounding-value hierarchy + */ + private BoundingValueHierarchy bvh; + /** + * native mesh used to construct this shape + */ + private CompoundMesh nativeMesh; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected MeshCollisionShape() { + } + + /** + * Instantiate a shape from the specified collection of native meshes. + * + * @param useCompression true to use quantized AABB compression + * @param meshes the collection on which to base the shape (must contain at + * least one triangle) + */ + public MeshCollisionShape( + boolean useCompression, Collection meshes) { + Validate.nonEmpty(meshes, "meshes"); + this.nativeMesh = new CompoundMesh(); + for (IndexedMesh submesh : meshes) { + nativeMesh.add(submesh); + } + Validate.require( + nativeMesh.countTriangles() > 0, "at least one triangle"); + + this.useCompression = useCompression; + createShape(); + } + + /** + * Instantiate a shape based on the specified CompoundMesh. + * + * @param useCompression true to use quantized AABB compression + * @param mesh the mesh on which to base the shape (not null, must contain + * at least one triangle, unaffected) + */ + public MeshCollisionShape(boolean useCompression, CompoundMesh mesh) { + Validate.require(mesh.countTriangles() > 0, "at least one triangle"); + + this.nativeMesh = new CompoundMesh(mesh); + this.useCompression = useCompression; + createShape(); + } + + /** + * Instantiate a shape from the specified native mesh(es). + * + * @param useCompression true to use quantized AABB compression + * @param submeshes the mesh(es) on which to base the shape (must contain at + * least one triangle) + */ + public MeshCollisionShape( + boolean useCompression, IndexedMesh... submeshes) { + Validate.nonEmpty(submeshes, "submeshes"); + this.nativeMesh = new CompoundMesh(); + for (IndexedMesh submesh : submeshes) { + nativeMesh.add(submesh); + } + Validate.require( + nativeMesh.countTriangles() > 0, "at least one triangle"); + + this.useCompression = useCompression; + createShape(); + } + + /** + * Instantiate a shape from the specified native mesh(es) and serialized + * BVH. The submeshes must be equivalent to those used to generate the BVH. + * + * @param bvhBytes the serialized BVH (not null, unaffected) + * @param submeshes the mesh(es) on which to base the shape (must contain at + * least one triangle) + */ + public MeshCollisionShape(byte[] bvhBytes, IndexedMesh... submeshes) { + Validate.nonNull(bvhBytes, "BVH data"); + Validate.nonEmpty(submeshes, "submeshes"); + this.nativeMesh = new CompoundMesh(); + for (IndexedMesh submesh : submeshes) { + nativeMesh.add(submesh); + } + Validate.require( + nativeMesh.countTriangles() > 0, "at least one triangle"); + + this.useCompression = true; + this.bvh = new BoundingValueHierarchy(bvhBytes); + createShape(); + } + + /** + * Instantiate a shape based on the specified JME mesh(es), using quantized + * AABB compression. + * + * @param jmeMeshes the mesh(es) on which to base the shape (must contain at + * least one triangle, unaffected) + */ + public MeshCollisionShape(Mesh... jmeMeshes) { + Validate.nonEmpty(jmeMeshes, "JME meshes"); + this.nativeMesh = new CompoundMesh(jmeMeshes); + Validate.require(nativeMesh.countTriangles() > 0, + "at least one triangle"); + + this.useCompression = true; + createShape(); + } + + /** + * Instantiate a shape based on the specified JME mesh. + * + * @param mesh the mesh on which to base the shape (must contain at least + * one triangle, unaffected) + * @param useCompression true to use quantized AABB compression + */ + public MeshCollisionShape(Mesh mesh, boolean useCompression) { + Validate.nonNull(mesh, "mesh"); + this.nativeMesh = new CompoundMesh(mesh); + Validate.require(nativeMesh.countTriangles() > 0, + "at least one triangle"); + + this.useCompression = useCompression; + createShape(); + } + // ************************************************************************* + // new methods exposed + + /** + * Count how many triangles are in the mesh. + * + * @return the count (≥0) + */ + public int countMeshTriangles() { + int result = nativeMesh.countTriangles(); + return result; + } + + /** + * Count how many vertices are in the mesh. + * + * @return the count (≥0) + */ + public int countMeshVertices() { + int numVertices = nativeMesh.countVertices(); + return numVertices; + } + + /** + * Serialize the BVH to a byte array. + * + * @return a new array containing a serialized version of the BVH + */ + public byte[] serializeBvh() { + byte[] result = bvh.serialize(); + return result; + } + + /** + * Attempt to divide this shape into 2 shapes. + * + * @param splittingTriangle to define the splitting plane (in shape + * coordinates, not null, unaffected) + * @return a pair of mesh shapes, the first shape generated by the plane's + * minus side and the 2nd shape generated by its plus side; either shape may + * be null, indicating an empty shape + */ + public MeshCollisionShape[] split(Triangle splittingTriangle) { + Validate.nonNull(splittingTriangle, "splitting triangle"); + + CompoundMesh[] mp = nativeMesh.split(splittingTriangle); + MeshCollisionShape[] result = new MeshCollisionShape[2]; + int numMinus = (mp[0] == null) ? 0 : mp[0].countTriangles(); + int numPlus = (mp[1] == null) ? 0 : mp[1].countTriangles(); + if (numMinus == 0 || numPlus == 0) { + // Degenerate case: all triangles lie to one side of the plane. + if (numMinus > 0) { + result[0] = this; + } else if (numPlus > 0) { + result[1] = this; + } + + } else { + result[0] = new MeshCollisionShape(useCompression, mp[0]); + result[0].setScale(scale); + + result[1] = new MeshCollisionShape(useCompression, mp[1]); + result[1].setScale(scale); + } + + return result; + } + // ************************************************************************* + // CollisionShape methods + + /** + * Test whether this shape can be split by an arbitrary plane. + * + * @return true if splittable, false otherwise + */ + @Override + public boolean canSplit() { + return true; + } + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned shape into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this shape (not null) + * @param original the instance from which this shape was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + + this.nativeMesh = cloner.clone(nativeMesh); + this.bvh = cloner.clone(bvh); + createShape(); + } + + /** + * De-serialize this shape from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + Platform writePlatform + = capsule.readEnum(tagNativePlatform, Platform.class, null); + if (writePlatform == null || writePlatform != JmeSystem.getPlatform()) { + this.bvh = null; // will re-generate the BVH for the new platform + } else { + this.bvh = (BoundingValueHierarchy) capsule.readSavable( + tagBvh, null); + } + + this.nativeMesh + = (CompoundMesh) capsule.readSavable(tagNativeMesh, null); + this.useCompression = capsule.readBoolean(tagUseCompression, true); + + createShape(); + } + + /** + * Recalculate this shape's bounding box if necessary. + */ + @Override + protected void recalculateAabb() { + long shapeId = nativeId(); + recalcAabb(shapeId); + } + + /** + * Serialize this shape to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(bvh, tagBvh, null); + + Platform nativePlatform = JmeSystem.getPlatform(); + capsule.write(nativePlatform, tagNativePlatform, null); + + capsule.write(nativeMesh, tagNativeMesh, null); + capsule.write(useCompression, tagUseCompression, true); + } + // ************************************************************************* + // Java private methods + + /** + * Instantiate the configured {@code btBvhTriangleMeshShape}. + */ + private void createShape() { + int numTriangles = nativeMesh.countTriangles(); + assert numTriangles > 0 : numTriangles; + + boolean buildBvh = (bvh == null); + long meshId = nativeMesh.nativeId(); + long shapeId = createShape(useCompression, buildBvh, meshId); + setNativeId(shapeId); + + if (buildBvh) { + this.bvh = new BoundingValueHierarchy(this); + } else { + long bvhId = bvh.nativeId(); + setOptimizedBvh(shapeId, bvhId); + } + + setContactFilterEnabled(enableContactFilter); + setScale(scale); + setMargin(margin); + } + // ************************************************************************* + // native private methods + + native private static long + createShape(boolean useCompression, boolean buildBvh, long meshId); + + native private static void recalcAabb(long shapeId); + + native private static void setOptimizedBvh(long shapeId, long bvhId); +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/BoundingValueHierarchy.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/BoundingValueHierarchy.java index 0f41c9e3e..4172f2745 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/BoundingValueHierarchy.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/BoundingValueHierarchy.java @@ -1,207 +1,207 @@ -/* - * Copyright (c) 2020-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.collision.shapes.infos; - -import com.jme3.bullet.NativePhysicsObject; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.util.clone.Cloner; -import com.jme3.util.clone.JmeCloneable; -import java.io.IOException; -import java.util.logging.Logger; -import jme3utilities.Validate; - -/** - * A Bounding-Value Hierarchy (BVH) generated for a MeshCollisionShape, based on - * Bullet's {@code btOptimizedBvh}. - * - * @author Stephen Gold sgold@sonic.net - */ -public class BoundingValueHierarchy - extends NativePhysicsObject - implements JmeCloneable, Savable { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(BoundingValueHierarchy.class.getName()); - /** - * field names for serialization - */ - final private static String tagBytes = "bytes"; - // ************************************************************************* - // constructors - - /** - * No-argument constructor needed by SavableClassUtil. - */ - protected BoundingValueHierarchy() { - } - - /** - * Instantiate a reference to the hierarchy of the specified - * MeshCollisionShape. Used internally. - * - * @param meshShape the pre-existing shape (not null) - */ - public BoundingValueHierarchy(MeshCollisionShape meshShape) { - Validate.nonNull(meshShape, "mesh shape"); - - long shapeId = meshShape.nativeId(); - long bvhId = getOptimizedBvh(shapeId); - super.setNativeIdNotTracked(bvhId); - } - - /** - * Instantiate a hierarchy from serialized bytes. - * - * @param bytes the serialized bytes (not null, unaffected) - */ - public BoundingValueHierarchy(byte[] bytes) { - Validate.nonNull(bytes, "bytes"); - - long bvhId = deSerialize(bytes); - super.setNativeId(bvhId); - } - // ************************************************************************* - // new methods exposed - - /** - * Serialize this hierarchy to a byte array. - * - * @return a new array containing serialized bytes (not null) - */ - public byte[] serialize() { - long bvhId = nativeId(); - byte[] result = serialize(bvhId); - - assert result != null; - return result; - } - // ************************************************************************* - // JmeCloneable methods - - /** - * Callback from {@link com.jme3.util.clone.Cloner} to convert this - * shallow-cloned hierarchy into a deep-cloned one, using the specified - * Cloner and original to resolve copied fields. - * - * @param cloner the Cloner that's cloning this hierarchy (not null) - * @param original the instance from which this hierarchy was shallow-cloned - * (not null, unaffected) - */ - @Override - public void cloneFields(Cloner cloner, Object original) { - BoundingValueHierarchy originalBvh = (BoundingValueHierarchy) original; - - byte[] bytes = originalBvh.serialize(); - long bvhId = deSerialize(bytes); - reassignNativeId(bvhId); - } - - /** - * Create a shallow clone for the JME cloner. - * - * @return a new instance - */ - @Override - public BoundingValueHierarchy jmeClone() { - try { - BoundingValueHierarchy clone = (BoundingValueHierarchy) clone(); - return clone; - } catch (CloneNotSupportedException exception) { - throw new RuntimeException(exception); - } - } - // ************************************************************************* - // Savable methods - - /** - * De-serialize this mesh from the specified importer, for example when - * loading from a J3O file. - * - * @param importer (not null) - * @throws IOException from the importer - */ - @Override - public void read(JmeImporter importer) throws IOException { - InputCapsule capsule = importer.getCapsule(this); - - byte[] bytes = capsule.readByteArray(tagBytes, null); - long bvhId = deSerialize(bytes); - setNativeId(bvhId); - } - - /** - * Serialize this mesh to the specified exporter, for example when saving to - * a J3O file. - * - * @param exporter (not null) - * @throws IOException from the exporter - */ - @Override - public void write(JmeExporter exporter) throws IOException { - OutputCapsule capsule = exporter.getCapsule(this); - - byte[] bytes = serialize(); - capsule.write(bytes, tagBytes, null); - } - // ************************************************************************* - // Java private methods - - /** - * Free the identified tracked native object. Invoked by reflection. - * - * @param bvhId the native identifier (not zero) - */ - private static void freeNativeObject(long bvhId) { - assert bvhId != 0L; - finalizeNative(bvhId); - } - // ************************************************************************* - // native private methods - - native private static long deSerialize(byte[] buffer); - - native private static void finalizeNative(long bvhId); - - native private static long getOptimizedBvh(long shapeId); - - native private static byte[] serialize(long bvhId); -} +/* + * Copyright (c) 2020-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.collision.shapes.infos; + +import com.jme3.bullet.NativePhysicsObject; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; +import java.io.IOException; +import java.util.logging.Logger; +import jme3utilities.Validate; + +/** + * A Bounding-Value Hierarchy (BVH) generated for a MeshCollisionShape, based on + * Bullet's {@code btOptimizedBvh}. + * + * @author Stephen Gold sgold@sonic.net + */ +public class BoundingValueHierarchy + extends NativePhysicsObject + implements JmeCloneable, Savable { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(BoundingValueHierarchy.class.getName()); + /** + * field names for serialization + */ + final private static String tagBytes = "bytes"; + // ************************************************************************* + // constructors + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected BoundingValueHierarchy() { + } + + /** + * Instantiate a reference to the hierarchy of the specified + * MeshCollisionShape. Used internally. + * + * @param meshShape the pre-existing shape (not null) + */ + public BoundingValueHierarchy(MeshCollisionShape meshShape) { + Validate.nonNull(meshShape, "mesh shape"); + + long shapeId = meshShape.nativeId(); + long bvhId = getOptimizedBvh(shapeId); + super.setNativeIdNotTracked(bvhId); + } + + /** + * Instantiate a hierarchy from serialized bytes. + * + * @param bytes the serialized bytes (not null, unaffected) + */ + public BoundingValueHierarchy(byte[] bytes) { + Validate.nonNull(bytes, "bytes"); + + long bvhId = deSerialize(bytes); + super.setNativeId(bvhId); + } + // ************************************************************************* + // new methods exposed + + /** + * Serialize this hierarchy to a byte array. + * + * @return a new array containing serialized bytes (not null) + */ + public byte[] serialize() { + long bvhId = nativeId(); + byte[] result = serialize(bvhId); + + assert result != null; + return result; + } + // ************************************************************************* + // JmeCloneable methods + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned hierarchy into a deep-cloned one, using the specified + * Cloner and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this hierarchy (not null) + * @param original the instance from which this hierarchy was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + BoundingValueHierarchy originalBvh = (BoundingValueHierarchy) original; + + byte[] bytes = originalBvh.serialize(); + long bvhId = deSerialize(bytes); + reassignNativeId(bvhId); + } + + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ + @Override + public BoundingValueHierarchy jmeClone() { + try { + BoundingValueHierarchy clone = (BoundingValueHierarchy) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } + // ************************************************************************* + // Savable methods + + /** + * De-serialize this mesh from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); + + byte[] bytes = capsule.readByteArray(tagBytes, null); + long bvhId = deSerialize(bytes); + setNativeId(bvhId); + } + + /** + * Serialize this mesh to the specified exporter, for example when saving to + * a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + OutputCapsule capsule = exporter.getCapsule(this); + + byte[] bytes = serialize(); + capsule.write(bytes, tagBytes, null); + } + // ************************************************************************* + // Java private methods + + /** + * Free the identified tracked native object. Invoked by reflection. + * + * @param bvhId the native identifier (not zero) + */ + private static void freeNativeObject(long bvhId) { + assert bvhId != 0L; + finalizeNative(bvhId); + } + // ************************************************************************* + // native private methods + + native private static long deSerialize(byte[] buffer); + + native private static void finalizeNative(long bvhId); + + native private static long getOptimizedBvh(long shapeId); + + native private static byte[] serialize(long bvhId); +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java index 98c9a6883..04b0f7438 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Auxiliary classes that relate to collision shapes. - */ -package com.jme3.bullet.collision.shapes.infos; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Auxiliary classes that relate to collision shapes. + */ +package com.jme3.bullet.collision.shapes.infos; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/package-info.java index c0cd5f76f..0a5a139df 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/collision/shapes/package-info.java @@ -1,36 +1,36 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Collision shapes, used to describe the shapes of Bullet collision objects - * other than soft bodies. - */ -package com.jme3.bullet.collision.shapes; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Collision shapes, used to describe the shapes of Bullet collision objects + * other than soft bodies. + */ +package com.jme3.bullet.collision.shapes; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/control/UseTriangles.java b/MinieLibrary/src/main/java/com/jme3/bullet/control/UseTriangles.java index a527f3aed..f8c1e9d41 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/control/UseTriangles.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/control/UseTriangles.java @@ -1,59 +1,59 @@ -/* - * Copyright (c) 2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.control; - -/** - * Enumerate uses for triangle indices when constructing a SoftBody from a Mesh. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum UseTriangles { - // ************************************************************************* - // values - - /** - * use triangle indices to generate soft-body faces only - */ - FacesOnly, - /** - * use triangle indices to generate soft-body faces and links - */ - FacesAndLinks, - /** - * ignore triangle indices - */ - Ignore, - /** - * use triangle indices to generate soft-body links only - */ - LinksOnly; -} +/* + * Copyright (c) 2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.control; + +/** + * Enumerate uses for triangle indices when constructing a SoftBody from a Mesh. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum UseTriangles { + // ************************************************************************* + // values + + /** + * use triangle indices to generate soft-body faces only + */ + FacesOnly, + /** + * use triangle indices to generate soft-body faces and links + */ + FacesAndLinks, + /** + * ignore triangle indices + */ + Ignore, + /** + * use triangle indices to generate soft-body links only + */ + LinksOnly; +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/control/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/control/package-info.java index a82ec6e67..56c2be67b 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/control/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/control/package-info.java @@ -1,36 +1,36 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Physics controls, used to link Bullet collision objects to particular - * spatials in a jMonkeyEngine scene graph. - */ -package com.jme3.bullet.control; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Physics controls, used to link Bullet collision objects to particular + * spatials in a jMonkeyEngine scene graph. + */ +package com.jme3.bullet.control; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/debug/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/debug/package-info.java index 0d12b0b4b..589bb8fa1 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/debug/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/debug/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Visualize physics objects for debugging. - */ -package com.jme3.bullet.debug; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Visualize physics objects for debugging. + */ +package com.jme3.bullet.debug; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/joints/JointEnd.java b/MinieLibrary/src/main/java/com/jme3/bullet/joints/JointEnd.java index f754ca4ed..ac99a53a9 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/joints/JointEnd.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/joints/JointEnd.java @@ -1,51 +1,51 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.joints; - -/** - * Enumerate the ends of a physics joint. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum JointEnd { - // ************************************************************************* - // values - - /** - * the first end - */ - A, - /** - * the 2nd end - */ - B -} +/* + * Copyright (c) 2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints; + +/** + * Enumerate the ends of a physics joint. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum JointEnd { + // ************************************************************************* + // values + + /** + * the first end + */ + A, + /** + * the 2nd end + */ + B +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/MotorParam.java b/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/MotorParam.java index 1fcad2a39..05e0a1b1a 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/MotorParam.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/MotorParam.java @@ -1,302 +1,302 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.joints.motors; - -/** - * Enumerate certain parameters of a RotationMotor or TranslationMotor, based on - * Bullet's btConstraintParams. - * - * @author Stephen Gold sgold@sonic.net - * @see RotationMotor#get(com.jme3.bullet.joints.motors.MotorParam) - * @see RotationMotor#set(com.jme3.bullet.joints.motors.MotorParam, float) - * @see TranslationMotor#get(com.jme3.bullet.joints.motors.MotorParam, - * com.jme3.math.Vector3f) - * @see TranslationMotor#set(com.jme3.bullet.joints.motors.MotorParam, - * com.jme3.math.Vector3f) - */ -public enum MotorParam { - // ************************************************************************* - // values - - /** - * restitution/bounce factor at the limits (m_bounce, default=0) - */ - Bounce, - /** - * spring's viscous damping ratio (m_springDamping, default=0) - */ - Damping, - /** - * spring's equilibrium point (m_equilibriumPoint, default=0) - */ - Equilibrium, - /** - * constraint lower limit (m_loLimit/m_lowerLimit, default=1[rot] or - * 0[translate]) - */ - LowerLimit, - /** - * maximum motor force (m_maxMotorForce, default=6[rot] or 0[translate]) - */ - MaxMotorForce, - /** - * constraint-force mixing parameter between limits (m_motorCFM, native - * name: BT_CONSTRAINT_CFM, default=0) - */ - MotorCfm, - /** - * error-reduction parameter between limits (m_motorERP, native name: - * BT_CONSTRAINT_ERP, default=0.9) - */ - MotorErp, - /** - * servo's target (m_servoTarget, default=0) - */ - ServoTarget, - /** - * spring's stiffness (m_springStiffness, default=0) - */ - Stiffness, - /** - * constraint-force mixing parameter at limits (m_stopCFM, native name: - * BT_CONSTRAINT_STOP_CFM, default=0) - */ - StopCfm, - /** - * error-reduction parameter at limits (m_stopERP, native name: - * BT_CONSTRAINT_STOP_ERP, default=0.2) - */ - StopErp, - /** - * motor's target velocity (m_targetVelocity, default=0) - */ - TargetVelocity, - /** - * constraint upper limit (m_hiLimit/m_upperLimit, default=-1[rot] or - * 0[translate]) - */ - UpperLimit; - // ************************************************************************* - // new methods exposed - - /** - * Test whether this parameter can be set to the specified value. - * - * @param value the desired parameter value - * @return true if settable, otherwise false - */ - public boolean canSet(float value) { - boolean result = (minValue() <= value) && (value <= maxValue()); - return result; - } - - /** - * Determine the default value for this parameter in a RotationMotor. - * - * @return the default parameter value - */ - public float defaultForRotationMotor() { - float result; - switch (this) { - case UpperLimit: - result = -1f; - break; - - case Bounce: - case Damping: - case Equilibrium: - case MotorCfm: - case ServoTarget: - case Stiffness: - case StopCfm: - case TargetVelocity: - result = 0f; - break; - - case StopErp: - result = 0.2f; - break; - - case MotorErp: - result = 0.9f; - break; - - case LowerLimit: - result = 1f; - break; - - case MaxMotorForce: - result = 6f; - break; - - default: - throw new IllegalArgumentException(toString()); - } - - return result; - } - - /** - * Determine the default value for this parameter in a TranslationMotor. - * - * @return the default parameter value - */ - public float defaultForTranslationMotor() { - float result; - switch (this) { - case Bounce: - case Damping: - case Equilibrium: - case LowerLimit: - case MaxMotorForce: - case MotorCfm: - case ServoTarget: - case Stiffness: - case StopCfm: - case TargetVelocity: - case UpperLimit: - result = 0f; - break; - - case StopErp: - result = 0.2f; - break; - - case MotorErp: - result = 0.9f; - break; - - default: - throw new IllegalArgumentException(toString()); - } - - return result; - } - - /** - * Determine the maximum value for this parameter. - * - * @return a maximum value, or Float.MAX_VALUE if there's no maximum - */ - public float maxValue() { - float result; - switch (this) { - case Bounce: - case MotorCfm: - case MotorErp: - case StopCfm: - case StopErp: - result = 1f; - break; - - case Damping: - case Equilibrium: - case LowerLimit: - case MaxMotorForce: - case ServoTarget: - case Stiffness: - case TargetVelocity: - case UpperLimit: - result = Float.MAX_VALUE; - break; - - default: - throw new IllegalArgumentException(toString()); - } - - return result; - } - - /** - * Determine the minimum value for this parameter. - * - * @return a minimum value, or -Float.MAX_VALUE if there's no minimum - */ - public float minValue() { - float result; - switch (this) { - case Equilibrium: - case LowerLimit: - case ServoTarget: - case TargetVelocity: - case UpperLimit: - result = -Float.MAX_VALUE; - break; - - case Bounce: - case Damping: - case MaxMotorForce: - case MotorCfm: - case MotorErp: - case Stiffness: - case StopCfm: - case StopErp: - result = 0f; - break; - - default: - throw new IllegalArgumentException(toString()); - } - - return result; - } - - /** - * Determine the parameter's index in native code. - * - * @return the index - */ - public int nativeIndex() { - switch (this) { - case MotorCfm: - return 3; - case MotorErp: - return 1; - case StopCfm: - return 4; - case StopErp: - return 2; - - default: - throw new IllegalArgumentException(toString()); - } - } - - /** - * Determine the serialization-tag suffix for this parameter. - * - * @return the tag suffix - */ - public String tagSuffix() { - return "_" + toString(); - } -} +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.joints.motors; + +/** + * Enumerate certain parameters of a RotationMotor or TranslationMotor, based on + * Bullet's btConstraintParams. + * + * @author Stephen Gold sgold@sonic.net + * @see RotationMotor#get(com.jme3.bullet.joints.motors.MotorParam) + * @see RotationMotor#set(com.jme3.bullet.joints.motors.MotorParam, float) + * @see TranslationMotor#get(com.jme3.bullet.joints.motors.MotorParam, + * com.jme3.math.Vector3f) + * @see TranslationMotor#set(com.jme3.bullet.joints.motors.MotorParam, + * com.jme3.math.Vector3f) + */ +public enum MotorParam { + // ************************************************************************* + // values + + /** + * restitution/bounce factor at the limits (m_bounce, default=0) + */ + Bounce, + /** + * spring's viscous damping ratio (m_springDamping, default=0) + */ + Damping, + /** + * spring's equilibrium point (m_equilibriumPoint, default=0) + */ + Equilibrium, + /** + * constraint lower limit (m_loLimit/m_lowerLimit, default=1[rot] or + * 0[translate]) + */ + LowerLimit, + /** + * maximum motor force (m_maxMotorForce, default=6[rot] or 0[translate]) + */ + MaxMotorForce, + /** + * constraint-force mixing parameter between limits (m_motorCFM, native + * name: BT_CONSTRAINT_CFM, default=0) + */ + MotorCfm, + /** + * error-reduction parameter between limits (m_motorERP, native name: + * BT_CONSTRAINT_ERP, default=0.9) + */ + MotorErp, + /** + * servo's target (m_servoTarget, default=0) + */ + ServoTarget, + /** + * spring's stiffness (m_springStiffness, default=0) + */ + Stiffness, + /** + * constraint-force mixing parameter at limits (m_stopCFM, native name: + * BT_CONSTRAINT_STOP_CFM, default=0) + */ + StopCfm, + /** + * error-reduction parameter at limits (m_stopERP, native name: + * BT_CONSTRAINT_STOP_ERP, default=0.2) + */ + StopErp, + /** + * motor's target velocity (m_targetVelocity, default=0) + */ + TargetVelocity, + /** + * constraint upper limit (m_hiLimit/m_upperLimit, default=-1[rot] or + * 0[translate]) + */ + UpperLimit; + // ************************************************************************* + // new methods exposed + + /** + * Test whether this parameter can be set to the specified value. + * + * @param value the desired parameter value + * @return true if settable, otherwise false + */ + public boolean canSet(float value) { + boolean result = (minValue() <= value) && (value <= maxValue()); + return result; + } + + /** + * Determine the default value for this parameter in a RotationMotor. + * + * @return the default parameter value + */ + public float defaultForRotationMotor() { + float result; + switch (this) { + case UpperLimit: + result = -1f; + break; + + case Bounce: + case Damping: + case Equilibrium: + case MotorCfm: + case ServoTarget: + case Stiffness: + case StopCfm: + case TargetVelocity: + result = 0f; + break; + + case StopErp: + result = 0.2f; + break; + + case MotorErp: + result = 0.9f; + break; + + case LowerLimit: + result = 1f; + break; + + case MaxMotorForce: + result = 6f; + break; + + default: + throw new IllegalArgumentException(toString()); + } + + return result; + } + + /** + * Determine the default value for this parameter in a TranslationMotor. + * + * @return the default parameter value + */ + public float defaultForTranslationMotor() { + float result; + switch (this) { + case Bounce: + case Damping: + case Equilibrium: + case LowerLimit: + case MaxMotorForce: + case MotorCfm: + case ServoTarget: + case Stiffness: + case StopCfm: + case TargetVelocity: + case UpperLimit: + result = 0f; + break; + + case StopErp: + result = 0.2f; + break; + + case MotorErp: + result = 0.9f; + break; + + default: + throw new IllegalArgumentException(toString()); + } + + return result; + } + + /** + * Determine the maximum value for this parameter. + * + * @return a maximum value, or Float.MAX_VALUE if there's no maximum + */ + public float maxValue() { + float result; + switch (this) { + case Bounce: + case MotorCfm: + case MotorErp: + case StopCfm: + case StopErp: + result = 1f; + break; + + case Damping: + case Equilibrium: + case LowerLimit: + case MaxMotorForce: + case ServoTarget: + case Stiffness: + case TargetVelocity: + case UpperLimit: + result = Float.MAX_VALUE; + break; + + default: + throw new IllegalArgumentException(toString()); + } + + return result; + } + + /** + * Determine the minimum value for this parameter. + * + * @return a minimum value, or -Float.MAX_VALUE if there's no minimum + */ + public float minValue() { + float result; + switch (this) { + case Equilibrium: + case LowerLimit: + case ServoTarget: + case TargetVelocity: + case UpperLimit: + result = -Float.MAX_VALUE; + break; + + case Bounce: + case Damping: + case MaxMotorForce: + case MotorCfm: + case MotorErp: + case Stiffness: + case StopCfm: + case StopErp: + result = 0f; + break; + + default: + throw new IllegalArgumentException(toString()); + } + + return result; + } + + /** + * Determine the parameter's index in native code. + * + * @return the index + */ + public int nativeIndex() { + switch (this) { + case MotorCfm: + return 3; + case MotorErp: + return 1; + case StopCfm: + return 4; + case StopErp: + return 2; + + default: + throw new IllegalArgumentException(toString()); + } + } + + /** + * Determine the serialization-tag suffix for this parameter. + * + * @return the tag suffix + */ + public String tagSuffix() { + return "_" + toString(); + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/package-info.java index 708a60e66..efdc0a272 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/joints/motors/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Classes that provide access to the motors in a SixDofJoint or New6Dof. - */ -package com.jme3.bullet.joints.motors; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Classes that provide access to the motors in a SixDofJoint or New6Dof. + */ +package com.jme3.bullet.joints.motors; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/joints/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/joints/package-info.java index 7b6fa1cdc..77eb714d2 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/joints/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/joints/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Physics joints based on Bullet's btTypedConstraint. - */ -package com.jme3.bullet.joints; +/* + * Copyright (c) 2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Physics joints based on Bullet's btTypedConstraint. + */ +package com.jme3.bullet.joints; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Aero.java b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Aero.java index c97d533b9..2faac28e3 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Aero.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Aero.java @@ -1,74 +1,74 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.objects.infos; - -/** - * Enumerate the implemented aerodynamic models for a SoftBodyConfig. - * - * @author Stephen Gold sgold@sonic.net - * @see SoftBodyConfig#aerodynamics() - */ -public enum Aero { - // ************************************************************************* - // values - - /** - * Vertex normals are oriented toward velocity. - */ - V_Point, - /** - * Vertex normals are flipped to match velocity. - */ - V_TwoSided, - /** - * Vertex normals are flipped to match velocity. Lift and drag forces are - * applied. - */ - V_TwoSidedLiftDrag, - /** - * Vertex normals are taken as they are. - */ - V_OneSided, - /** - * Face normals are flipped to match velocity. - */ - F_TwoSided, - /** - * Face normals are flipped to match velocity. Lift and drag forces are - * applied. - */ - F_TwoSidedLiftDrag, - /** - * Face normals are taken as they are. - */ - F_OneSided -} +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects.infos; + +/** + * Enumerate the implemented aerodynamic models for a SoftBodyConfig. + * + * @author Stephen Gold sgold@sonic.net + * @see SoftBodyConfig#aerodynamics() + */ +public enum Aero { + // ************************************************************************* + // values + + /** + * Vertex normals are oriented toward velocity. + */ + V_Point, + /** + * Vertex normals are flipped to match velocity. + */ + V_TwoSided, + /** + * Vertex normals are flipped to match velocity. Lift and drag forces are + * applied. + */ + V_TwoSidedLiftDrag, + /** + * Vertex normals are taken as they are. + */ + V_OneSided, + /** + * Face normals are flipped to match velocity. + */ + F_TwoSided, + /** + * Face normals are flipped to match velocity. Lift and drag forces are + * applied. + */ + F_TwoSidedLiftDrag, + /** + * Face normals are taken as they are. + */ + F_OneSided +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Cluster.java b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Cluster.java index fd0b941ba..e44bc4f6a 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Cluster.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Cluster.java @@ -1,153 +1,153 @@ -/* - * Copyright (c) 2019-2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.objects.infos; - -/** - * Enumerate the float-valued parameters in a soft-body cluster. - * - * @author Stephen Gold sgold@sonic.net - * @see com.jme3.bullet.objects.PhysicsSoftBody#set(Cluster, int, float) - */ -public enum Cluster { - // ************************************************************************* - // values - - /** - * angular damping coefficient (default=0, native field: m_adamping) - */ - AngularDamping, - /** - * linear damping coefficient (default=0, native field: m_ldamping) - */ - LinearDamping, - /** - * matching coefficient (default=0, native field: m_matching) - */ - Matching, - /** - * maximum self-collision impulse (default=100, native field: - * m_maxSelfCollisionImpulse) - */ - MaxSelfImpulse, - /** - * node-damping coefficient (default=0, native field: m_ndamping) - */ - NodeDamping, - /** - * self-collision impulse factor (default=0.01, native field: - * m_selfCollisionImpulseFactor) - */ - SelfImpulse; - // ************************************************************************* - // new methods exposed - - /** - * Test whether this parameter can be set to the specified value. - * - * @param value the desired parameter value - * @return true if settable, otherwise false - */ - public boolean canSet(float value) { - boolean result = (value >= minValue()) && (value <= maxValue()); - return result; - } - - /** - * Determine the default value for this parameter. - * - * @return the default parameter value - */ - public float defValue() { - float result; - switch (this) { - case AngularDamping: - case LinearDamping: - case Matching: - case NodeDamping: - result = 0f; - break; - - case MaxSelfImpulse: - result = 100f; - break; - - case SelfImpulse: - result = 0.01f; - break; - - default: - throw new IllegalArgumentException("parameter = " + this); - } - - return result; - } - - /** - * Determine the maximum value for this parameter. - * - * @return a maximum value, or Float.MAX_VALUE if there's no maximum - */ - public float maxValue() { - switch (this) { - case AngularDamping: - case LinearDamping: - case Matching: - case MaxSelfImpulse: - case NodeDamping: - case SelfImpulse: - return Float.MAX_VALUE; - - default: - throw new IllegalArgumentException("parameter = " + this); - } - } - - /** - * Determine the minimum value for this parameter. - * - * @return a minimum value, or -Float.MAX_VALUE if there's no minimum - */ - public float minValue() { - switch (this) { - case AngularDamping: - case LinearDamping: - case Matching: - case MaxSelfImpulse: - case NodeDamping: - case SelfImpulse: - return -Float.MAX_VALUE; - - default: - throw new IllegalArgumentException("parameter = " + this); - } - } -} +/* + * Copyright (c) 2019-2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects.infos; + +/** + * Enumerate the float-valued parameters in a soft-body cluster. + * + * @author Stephen Gold sgold@sonic.net + * @see com.jme3.bullet.objects.PhysicsSoftBody#set(Cluster, int, float) + */ +public enum Cluster { + // ************************************************************************* + // values + + /** + * angular damping coefficient (default=0, native field: m_adamping) + */ + AngularDamping, + /** + * linear damping coefficient (default=0, native field: m_ldamping) + */ + LinearDamping, + /** + * matching coefficient (default=0, native field: m_matching) + */ + Matching, + /** + * maximum self-collision impulse (default=100, native field: + * m_maxSelfCollisionImpulse) + */ + MaxSelfImpulse, + /** + * node-damping coefficient (default=0, native field: m_ndamping) + */ + NodeDamping, + /** + * self-collision impulse factor (default=0.01, native field: + * m_selfCollisionImpulseFactor) + */ + SelfImpulse; + // ************************************************************************* + // new methods exposed + + /** + * Test whether this parameter can be set to the specified value. + * + * @param value the desired parameter value + * @return true if settable, otherwise false + */ + public boolean canSet(float value) { + boolean result = (value >= minValue()) && (value <= maxValue()); + return result; + } + + /** + * Determine the default value for this parameter. + * + * @return the default parameter value + */ + public float defValue() { + float result; + switch (this) { + case AngularDamping: + case LinearDamping: + case Matching: + case NodeDamping: + result = 0f; + break; + + case MaxSelfImpulse: + result = 100f; + break; + + case SelfImpulse: + result = 0.01f; + break; + + default: + throw new IllegalArgumentException("parameter = " + this); + } + + return result; + } + + /** + * Determine the maximum value for this parameter. + * + * @return a maximum value, or Float.MAX_VALUE if there's no maximum + */ + public float maxValue() { + switch (this) { + case AngularDamping: + case LinearDamping: + case Matching: + case MaxSelfImpulse: + case NodeDamping: + case SelfImpulse: + return Float.MAX_VALUE; + + default: + throw new IllegalArgumentException("parameter = " + this); + } + } + + /** + * Determine the minimum value for this parameter. + * + * @return a minimum value, or -Float.MAX_VALUE if there's no minimum + */ + public float minValue() { + switch (this) { + case AngularDamping: + case LinearDamping: + case Matching: + case MaxSelfImpulse: + case NodeDamping: + case SelfImpulse: + return -Float.MAX_VALUE; + + default: + throw new IllegalArgumentException("parameter = " + this); + } + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Sbcp.java b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Sbcp.java index 4799517b9..39087a3aa 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Sbcp.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/Sbcp.java @@ -1,274 +1,274 @@ -/* - * Copyright (c) 2019-2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.objects.infos; - -/** - * Enumerate the float-valued parameters in a SoftBodyConfig. - * - * @author Stephen Gold sgold@sonic.net - * @see SoftBodyConfig#get(Sbcp) - */ -public enum Sbcp { - // ************************************************************************* - // values - - /** - * anchor hardness coefficient (≥0, ≤1, default=0.7, native field: - * kAHR) - */ - AnchorHardness, - /** - * cluster-versus-kinetic hardness coefficient (≥0, ≤1, default=1, - * native field: kSKHR_CL) - */ - ClusterKineticHardness, - /** - * cluster-versus-kinetic impulse-split coefficient (≥0, ≤1, - * default=0.5, native field: kSK_SPLT_CL) - */ - ClusterKineticSplit, - /** - * cluster-versus-rigidBody hardness coefficient (≥0, ≤1, default=0.1, - * native field: kSRHR_CL) - */ - ClusterRigidHardness, - /** - * cluster-versus-rigidBody impulse-split coefficient (≥0, ≤1, - * default=0.5, native field: kSR_SPLT_CL) - */ - ClusterRigidSplit, - /** - * cluster-versus-softBody hardness coefficient (≥0, ≤1, default=0.5, - * native field: kSSHR_CL) - */ - ClusterSoftHardness, - /** - * cluster-versus-softBody impulse-split coefficient (≥0, ≤1, - * default=0.5, native field: kSS_SPLT_CL). - */ - ClusterSoftSplit, - /** - * damping coefficient (≥0, ≤1, default=0, native field: kDP) - */ - Damping, - /** - * drag coefficient (≥0, default=0, native field: kDG) - */ - Drag, - /** - * dynamic friction coefficient (≥0, <1, default=0.2, native field: - * kDF) - */ - DynamicFriction, - /** - * contact hardness coefficient for static or kinematic rigid bodies (≥0, - * ≤1, default=0.1, native field: kKHR) - */ - KineticHardness, - /** - * lift coefficient (≥0, default=0, native field: kLF) - */ - Lift, - /** - * maximum volume ratio for the pose (default=1, native field: maxvolume) - */ - MaxVolumeRatio, - /** - * pose-matching coefficient: how strongly the soft body will tend to return - * to its default pose (≥0, ≤1, default=0, native field: kMT) - */ - PoseMatching, - /** - * pressure coefficient (default=0, native field: kPR) - */ - Pressure, - /** - * contact hardness coefficient for dynamic rigid bodies (≥0, ≤1, - * default=1, native field: kCHR) - */ - RigidHardness, - /** - * soft-body contact hardness coefficient (≥0, ≤1, default=1, native - * field: kSHR) - */ - SoftHardness, - /** - * time scale (default=1, native field: timescale) - */ - TimeScale, - /** - * velocity correction factor (Baumgarte) (default=1, native field: kVCF) - */ - VelocityCorrection, - /** - * volume conservation coefficient (≥0, default=0, native field: kVC) - */ - VolumeConservation; - // ************************************************************************* - // new methods exposed - - /** - * Test whether this parameter can be set to the specified value. - * - * @param value the desired parameter value - * @return true if settable, otherwise false - */ - public boolean canSet(float value) { - boolean result = (minValue() <= value) && (value <= maxValue()); - return result; - } - - /** - * Determine the default value for this parameter. - * - * @return the default parameter value - */ - public float defValue() { - float result; - switch (this) { - case Damping: - case Drag: - case Lift: - case PoseMatching: - case Pressure: - case VolumeConservation: - result = 0f; - break; - - case ClusterRigidHardness: - case KineticHardness: - result = 0.1f; - break; - - case DynamicFriction: - result = 0.2f; - break; - - case ClusterKineticSplit: - case ClusterRigidSplit: - case ClusterSoftHardness: - case ClusterSoftSplit: - result = 0.5f; - break; - - case AnchorHardness: - result = 0.7f; - break; - - case ClusterKineticHardness: - case MaxVolumeRatio: - case RigidHardness: - case SoftHardness: - case TimeScale: - case VelocityCorrection: - result = 1f; - break; - - default: - throw new IllegalArgumentException("parameter = " + this); - } - - return result; - } - - /** - * Determine the maximum value for this parameter. - * - * @return a maximum value, or Float.MAX_VALUE if there's no maximum - */ - public float maxValue() { - switch (this) { - case AnchorHardness: - case ClusterKineticHardness: - case ClusterKineticSplit: - case ClusterRigidHardness: - case ClusterRigidSplit: - case ClusterSoftHardness: - case ClusterSoftSplit: - case DynamicFriction: - case KineticHardness: - case PoseMatching: - case RigidHardness: - case SoftHardness: - return 1f; - - case Damping: - case Drag: - case Lift: - case MaxVolumeRatio: - case Pressure: - case TimeScale: - case VelocityCorrection: - case VolumeConservation: - return Float.MAX_VALUE; - - default: - throw new IllegalArgumentException("parameter = " + this); - } - } - - /** - * Determine the minimum value for this parameter. - * - * @return a minimum value, or -Float.MAX_VALUE if there's no minimum - */ - public float minValue() { - switch (this) { - case MaxVolumeRatio: - case Pressure: - case TimeScale: - case VelocityCorrection: - return -Float.MAX_VALUE; - - case AnchorHardness: - case ClusterKineticHardness: - case ClusterKineticSplit: - case ClusterRigidHardness: - case ClusterRigidSplit: - case ClusterSoftHardness: - case ClusterSoftSplit: - case Damping: - case Drag: - case DynamicFriction: - case KineticHardness: - case Lift: - case PoseMatching: - case RigidHardness: - case SoftHardness: - case VolumeConservation: - return 0f; - - default: - throw new IllegalArgumentException("parameter = " + this); - } - } -} +/* + * Copyright (c) 2019-2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.objects.infos; + +/** + * Enumerate the float-valued parameters in a SoftBodyConfig. + * + * @author Stephen Gold sgold@sonic.net + * @see SoftBodyConfig#get(Sbcp) + */ +public enum Sbcp { + // ************************************************************************* + // values + + /** + * anchor hardness coefficient (≥0, ≤1, default=0.7, native field: + * kAHR) + */ + AnchorHardness, + /** + * cluster-versus-kinetic hardness coefficient (≥0, ≤1, default=1, + * native field: kSKHR_CL) + */ + ClusterKineticHardness, + /** + * cluster-versus-kinetic impulse-split coefficient (≥0, ≤1, + * default=0.5, native field: kSK_SPLT_CL) + */ + ClusterKineticSplit, + /** + * cluster-versus-rigidBody hardness coefficient (≥0, ≤1, default=0.1, + * native field: kSRHR_CL) + */ + ClusterRigidHardness, + /** + * cluster-versus-rigidBody impulse-split coefficient (≥0, ≤1, + * default=0.5, native field: kSR_SPLT_CL) + */ + ClusterRigidSplit, + /** + * cluster-versus-softBody hardness coefficient (≥0, ≤1, default=0.5, + * native field: kSSHR_CL) + */ + ClusterSoftHardness, + /** + * cluster-versus-softBody impulse-split coefficient (≥0, ≤1, + * default=0.5, native field: kSS_SPLT_CL). + */ + ClusterSoftSplit, + /** + * damping coefficient (≥0, ≤1, default=0, native field: kDP) + */ + Damping, + /** + * drag coefficient (≥0, default=0, native field: kDG) + */ + Drag, + /** + * dynamic friction coefficient (≥0, <1, default=0.2, native field: + * kDF) + */ + DynamicFriction, + /** + * contact hardness coefficient for static or kinematic rigid bodies (≥0, + * ≤1, default=0.1, native field: kKHR) + */ + KineticHardness, + /** + * lift coefficient (≥0, default=0, native field: kLF) + */ + Lift, + /** + * maximum volume ratio for the pose (default=1, native field: maxvolume) + */ + MaxVolumeRatio, + /** + * pose-matching coefficient: how strongly the soft body will tend to return + * to its default pose (≥0, ≤1, default=0, native field: kMT) + */ + PoseMatching, + /** + * pressure coefficient (default=0, native field: kPR) + */ + Pressure, + /** + * contact hardness coefficient for dynamic rigid bodies (≥0, ≤1, + * default=1, native field: kCHR) + */ + RigidHardness, + /** + * soft-body contact hardness coefficient (≥0, ≤1, default=1, native + * field: kSHR) + */ + SoftHardness, + /** + * time scale (default=1, native field: timescale) + */ + TimeScale, + /** + * velocity correction factor (Baumgarte) (default=1, native field: kVCF) + */ + VelocityCorrection, + /** + * volume conservation coefficient (≥0, default=0, native field: kVC) + */ + VolumeConservation; + // ************************************************************************* + // new methods exposed + + /** + * Test whether this parameter can be set to the specified value. + * + * @param value the desired parameter value + * @return true if settable, otherwise false + */ + public boolean canSet(float value) { + boolean result = (minValue() <= value) && (value <= maxValue()); + return result; + } + + /** + * Determine the default value for this parameter. + * + * @return the default parameter value + */ + public float defValue() { + float result; + switch (this) { + case Damping: + case Drag: + case Lift: + case PoseMatching: + case Pressure: + case VolumeConservation: + result = 0f; + break; + + case ClusterRigidHardness: + case KineticHardness: + result = 0.1f; + break; + + case DynamicFriction: + result = 0.2f; + break; + + case ClusterKineticSplit: + case ClusterRigidSplit: + case ClusterSoftHardness: + case ClusterSoftSplit: + result = 0.5f; + break; + + case AnchorHardness: + result = 0.7f; + break; + + case ClusterKineticHardness: + case MaxVolumeRatio: + case RigidHardness: + case SoftHardness: + case TimeScale: + case VelocityCorrection: + result = 1f; + break; + + default: + throw new IllegalArgumentException("parameter = " + this); + } + + return result; + } + + /** + * Determine the maximum value for this parameter. + * + * @return a maximum value, or Float.MAX_VALUE if there's no maximum + */ + public float maxValue() { + switch (this) { + case AnchorHardness: + case ClusterKineticHardness: + case ClusterKineticSplit: + case ClusterRigidHardness: + case ClusterRigidSplit: + case ClusterSoftHardness: + case ClusterSoftSplit: + case DynamicFriction: + case KineticHardness: + case PoseMatching: + case RigidHardness: + case SoftHardness: + return 1f; + + case Damping: + case Drag: + case Lift: + case MaxVolumeRatio: + case Pressure: + case TimeScale: + case VelocityCorrection: + case VolumeConservation: + return Float.MAX_VALUE; + + default: + throw new IllegalArgumentException("parameter = " + this); + } + } + + /** + * Determine the minimum value for this parameter. + * + * @return a minimum value, or -Float.MAX_VALUE if there's no minimum + */ + public float minValue() { + switch (this) { + case MaxVolumeRatio: + case Pressure: + case TimeScale: + case VelocityCorrection: + return -Float.MAX_VALUE; + + case AnchorHardness: + case ClusterKineticHardness: + case ClusterKineticSplit: + case ClusterRigidHardness: + case ClusterRigidSplit: + case ClusterSoftHardness: + case ClusterSoftSplit: + case Damping: + case Drag: + case DynamicFriction: + case KineticHardness: + case Lift: + case PoseMatching: + case RigidHardness: + case SoftHardness: + case VolumeConservation: + return 0f; + + default: + throw new IllegalArgumentException("parameter = " + this); + } + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/package-info.java index 38872176f..fa5ffd91a 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/objects/infos/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Auxiliary classes that relate to collision objects. - */ -package com.jme3.bullet.objects.infos; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Auxiliary classes that relate to collision objects. + */ +package com.jme3.bullet.objects.infos; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/objects/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/objects/package-info.java index 6363ad0e8..1bf94a698 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/objects/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/objects/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Collision-object classes. - */ -package com.jme3.bullet.objects; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Collision-object classes. + */ +package com.jme3.bullet.objects; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/package-info.java index 8b68a13fb..2440cd2dd 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * PhysicsSpace and some associated classes and interfaces. - */ -package com.jme3.bullet; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * PhysicsSpace and some associated classes and interfaces. + */ +package com.jme3.bullet; diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/util/DebugMeshKey.java b/MinieLibrary/src/main/java/com/jme3/bullet/util/DebugMeshKey.java index 4f9f7da6b..51c3b0599 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/util/DebugMeshKey.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/util/DebugMeshKey.java @@ -1,169 +1,169 @@ -/* - * Copyright (c) 2018-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.util; - -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; -import jme3utilities.MeshNormals; - -/** - * Key used to locate cached debug meshes. Note: immutable. - * - * @author Stephen Gold sgold@sonic.net - */ -class DebugMeshKey { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(DebugMeshKey.class.getName()); - // ************************************************************************* - // fields - - /** - * margin of the CollisionShape - */ - final private float margin; - /** - * desired mesh resolution - */ - final private int resolution; - /** - * object ID of the CollisionShape - */ - final private long shapeId; - /** - * option for normals in the debug mesh - */ - final private MeshNormals normals; - /** - * scale factors of the CollisionShape - */ - final private Vector3f scale; - // ************************************************************************* - // constructors - - /** - * Instantiate a new key. - * - * @param shape (not null, not compound, unaffected) - * @param normals (not null) - * @param resolution 0 or 1 - */ - DebugMeshKey(CollisionShape shape, MeshNormals normals, int resolution) { - assert normals != null; - assert !(shape instanceof CompoundCollisionShape); - - this.normals = normals; - this.margin = shape.getMargin(); - if (shape.isConvex()) { - this.resolution = resolution; - } else { - this.resolution = DebugShapeFactory.lowResolution; - } - this.shapeId = shape.nativeId(); - this.scale = shape.getScale(null); - } - // ************************************************************************* - // new methods exposed - - /** - * Read the shape ID of this key. - * - * @return the ID value - */ - long shapeId() { - return shapeId; - } - // ************************************************************************* - // Object methods - - /** - * Test for exact equivalence with another Object. - * - * @param otherObject the object to compare to (may be null, unaffected) - * @return true if the objects are equivalent, otherwise false - */ - @Override - public boolean equals(Object otherObject) { - boolean result; - if (otherObject == this) { - result = true; - } else if (otherObject != null - && otherObject.getClass() == getClass()) { - DebugMeshKey otherKey = (DebugMeshKey) otherObject; - result = (shapeId == otherKey.shapeId) - && scale.equals(otherKey.scale) - && (Float.compare(margin, otherKey.margin) == 0) - && (normals == otherKey.normals) - && (resolution == otherKey.resolution); - } else { - result = false; - } - - return result; - } - - /** - * Generate the hash code for this key. - * - * @return value for use in hashing - */ - @Override - public int hashCode() { - int hash = (int) (shapeId >> 4); - hash = 7 * hash + scale.hashCode(); - hash = 7 * hash + Float.floatToIntBits(margin); - hash = 7 * hash + resolution; - hash = 7 * hash + normals.ordinal(); - - return hash; - } - - /** - * Represent this key as a text string. - * - * @return descriptive string of text (not null, not empty) - */ - @Override - public String toString() { - String desc = String.format( - "shape=%x scale=%s margin=%f res=%d normals=%s", - shapeId, scale, margin, resolution, normals); - return desc; - } -} +/* + * Copyright (c) 2018-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; +import jme3utilities.MeshNormals; + +/** + * Key used to locate cached debug meshes. Note: immutable. + * + * @author Stephen Gold sgold@sonic.net + */ +class DebugMeshKey { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(DebugMeshKey.class.getName()); + // ************************************************************************* + // fields + + /** + * margin of the CollisionShape + */ + final private float margin; + /** + * desired mesh resolution + */ + final private int resolution; + /** + * object ID of the CollisionShape + */ + final private long shapeId; + /** + * option for normals in the debug mesh + */ + final private MeshNormals normals; + /** + * scale factors of the CollisionShape + */ + final private Vector3f scale; + // ************************************************************************* + // constructors + + /** + * Instantiate a new key. + * + * @param shape (not null, not compound, unaffected) + * @param normals (not null) + * @param resolution 0 or 1 + */ + DebugMeshKey(CollisionShape shape, MeshNormals normals, int resolution) { + assert normals != null; + assert !(shape instanceof CompoundCollisionShape); + + this.normals = normals; + this.margin = shape.getMargin(); + if (shape.isConvex()) { + this.resolution = resolution; + } else { + this.resolution = DebugShapeFactory.lowResolution; + } + this.shapeId = shape.nativeId(); + this.scale = shape.getScale(null); + } + // ************************************************************************* + // new methods exposed + + /** + * Read the shape ID of this key. + * + * @return the ID value + */ + long shapeId() { + return shapeId; + } + // ************************************************************************* + // Object methods + + /** + * Test for exact equivalence with another Object. + * + * @param otherObject the object to compare to (may be null, unaffected) + * @return true if the objects are equivalent, otherwise false + */ + @Override + public boolean equals(Object otherObject) { + boolean result; + if (otherObject == this) { + result = true; + } else if (otherObject != null + && otherObject.getClass() == getClass()) { + DebugMeshKey otherKey = (DebugMeshKey) otherObject; + result = (shapeId == otherKey.shapeId) + && scale.equals(otherKey.scale) + && (Float.compare(margin, otherKey.margin) == 0) + && (normals == otherKey.normals) + && (resolution == otherKey.resolution); + } else { + result = false; + } + + return result; + } + + /** + * Generate the hash code for this key. + * + * @return value for use in hashing + */ + @Override + public int hashCode() { + int hash = (int) (shapeId >> 4); + hash = 7 * hash + scale.hashCode(); + hash = 7 * hash + Float.floatToIntBits(margin); + hash = 7 * hash + resolution; + hash = 7 * hash + normals.ordinal(); + + return hash; + } + + /** + * Represent this key as a text string. + * + * @return descriptive string of text (not null, not empty) + */ + @Override + public String toString() { + String desc = String.format( + "shape=%x scale=%s margin=%f res=%d normals=%s", + shapeId, scale, margin, resolution, normals); + return desc; + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java b/MinieLibrary/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java index a0b941f52..106ff2027 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java @@ -1,50 +1,50 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.util; - -/** - * A utility class for interfacing with Native Bullet. If this class doesn't - * exist, JME won't load the native library. - * - * @author normenhansen - */ -final public class NativeMeshUtil { - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private NativeMeshUtil() { - // do nothing - } -} +/* + * Copyright (c) 2009-2018 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bullet.util; + +/** + * A utility class for interfacing with Native Bullet. If this class doesn't + * exist, JME won't load the native library. + * + * @author normenhansen + */ +final public class NativeMeshUtil { + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private NativeMeshUtil() { + // do nothing + } +} diff --git a/MinieLibrary/src/main/java/com/jme3/bullet/util/package-info.java b/MinieLibrary/src/main/java/com/jme3/bullet/util/package-info.java index cb6354bac..453443c77 100644 --- a/MinieLibrary/src/main/java/com/jme3/bullet/util/package-info.java +++ b/MinieLibrary/src/main/java/com/jme3/bullet/util/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Utility classes for Bullet physics and related classes. - */ -package com.jme3.bullet.util; +/* + * Copyright (c) 2019 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Utility classes for Bullet physics and related classes. + */ +package com.jme3.bullet.util; diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/AppDataFilter.java b/MinieLibrary/src/main/java/jme3utilities/minie/AppDataFilter.java index 57a764e9b..229626514 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/AppDataFilter.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/AppDataFilter.java @@ -1,105 +1,105 @@ -/* - Copyright (c) 2019-2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsBody; -import java.util.logging.Logger; - -/** - * A simple DebugAppStateFilter that selects physics objects associated with a - * specific application-data object. Instances are immutable. - * - * @author Stephen Gold sgold@sonic.net - */ -public class AppDataFilter implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(AppDataFilter.class.getName()); - // ************************************************************************* - // fields - - /** - * application-data object (may be null) - */ - final private Object appData; - // ************************************************************************* - // constructors - - /** - * Instantiate a filter for the specified application-data object. - * - * @param appData the desired object (alias created) or null to display/dump - * objects without application data - */ - public AppDataFilter(Object appData) { - this.appData = appData; - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object should be displayed/dumped. - * - * @param physicsObject the joint or collision object to test (unaffected) - * @return return true if physicsObject should be displayed/dumped, false if - * it shouldn't be - */ - @Override - public boolean displayObject(Object physicsObject) { - boolean result = false; - - if (physicsObject instanceof PhysicsCollisionObject) { - PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; - if (pco.getApplicationData() == appData) { - result = true; - } - - } else if (physicsObject instanceof PhysicsJoint) { - PhysicsJoint joint = (PhysicsJoint) physicsObject; - PhysicsBody a = joint.getBody(JointEnd.A); - if (a != null && a.getApplicationData() == appData) { - result = true; - } else { - PhysicsBody b = joint.getBody(JointEnd.B); - if (b != null && b.getApplicationData() == appData) { - result = true; - } - } - } - - return result; - } -} +/* + Copyright (c) 2019-2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsBody; +import java.util.logging.Logger; + +/** + * A simple DebugAppStateFilter that selects physics objects associated with a + * specific application-data object. Instances are immutable. + * + * @author Stephen Gold sgold@sonic.net + */ +public class AppDataFilter implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(AppDataFilter.class.getName()); + // ************************************************************************* + // fields + + /** + * application-data object (may be null) + */ + final private Object appData; + // ************************************************************************* + // constructors + + /** + * Instantiate a filter for the specified application-data object. + * + * @param appData the desired object (alias created) or null to display/dump + * objects without application data + */ + public AppDataFilter(Object appData) { + this.appData = appData; + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object should be displayed/dumped. + * + * @param physicsObject the joint or collision object to test (unaffected) + * @return return true if physicsObject should be displayed/dumped, false if + * it shouldn't be + */ + @Override + public boolean displayObject(Object physicsObject) { + boolean result = false; + + if (physicsObject instanceof PhysicsCollisionObject) { + PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; + if (pco.getApplicationData() == appData) { + result = true; + } + + } else if (physicsObject instanceof PhysicsJoint) { + PhysicsJoint joint = (PhysicsJoint) physicsObject; + PhysicsBody a = joint.getBody(JointEnd.A); + if (a != null && a.getApplicationData() == appData) { + result = true; + } else { + PhysicsBody b = joint.getBody(JointEnd.B); + if (b != null && b.getApplicationData() == appData) { + result = true; + } + } + } + + return result; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/CcdFilter.java b/MinieLibrary/src/main/java/jme3utilities/minie/CcdFilter.java index e1f250288..f6e591c6c 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/CcdFilter.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/CcdFilter.java @@ -1,86 +1,86 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.CollisionSpace; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.objects.PhysicsRigidBody; -import java.util.logging.Logger; - -/** - * A simple DebugAppStateFilter that selects dynamic rigid bodies for which - * continuous collision detection (CCD) is active. - * - * @author Stephen Gold sgold@sonic.net - */ -public class CcdFilter implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(CcdFilter.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a filter. - */ - public CcdFilter() { // made explicit to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object should be displayed/dumped. - * - * @param physicsObject the joint or collision object to test (unaffected) - * @return return true if the object should be displayed/dumped, false if it - * shouldn't be - */ - @Override - public boolean displayObject(Object physicsObject) { - if (physicsObject instanceof PhysicsRigidBody) { - PhysicsRigidBody rigidBody = (PhysicsRigidBody) physicsObject; - CollisionSpace space = rigidBody.getCollisionSpace(); - if (rigidBody.isDynamic() && space instanceof PhysicsSpace) { - float timeStep = ((PhysicsSpace) space).getAccuracy(); - float squaredSpeed = rigidBody.getSquaredSpeed(); - float squareMotion = squaredSpeed * timeStep * timeStep; - float threshold = rigidBody.getCcdSquareMotionThreshold(); - if (squareMotion > threshold) { - return true; - } - } - } - - return false; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.CollisionSpace; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.objects.PhysicsRigidBody; +import java.util.logging.Logger; + +/** + * A simple DebugAppStateFilter that selects dynamic rigid bodies for which + * continuous collision detection (CCD) is active. + * + * @author Stephen Gold sgold@sonic.net + */ +public class CcdFilter implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(CcdFilter.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a filter. + */ + public CcdFilter() { // made explicit to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object should be displayed/dumped. + * + * @param physicsObject the joint or collision object to test (unaffected) + * @return return true if the object should be displayed/dumped, false if it + * shouldn't be + */ + @Override + public boolean displayObject(Object physicsObject) { + if (physicsObject instanceof PhysicsRigidBody) { + PhysicsRigidBody rigidBody = (PhysicsRigidBody) physicsObject; + CollisionSpace space = rigidBody.getCollisionSpace(); + if (rigidBody.isDynamic() && space instanceof PhysicsSpace) { + float timeStep = ((PhysicsSpace) space).getAccuracy(); + float squaredSpeed = rigidBody.getSquaredSpeed(); + float squareMotion = squaredSpeed * timeStep * timeStep; + float threshold = rigidBody.getCcdSquareMotionThreshold(); + if (squareMotion > threshold) { + return true; + } + } + } + + return false; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/ClassFilter.java b/MinieLibrary/src/main/java/jme3utilities/minie/ClassFilter.java index 3104d17dd..d0920476e 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/ClassFilter.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/ClassFilter.java @@ -1,83 +1,83 @@ -/* - Copyright (c) 2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.debug.BulletDebugAppState; -import java.util.logging.Logger; - -/** - * A simple DebugAppStateFilter to select objects that are assignable to a - * specific class. Instances are immutable. - * - * @author Stephen Gold sgold@sonic.net - */ -public class ClassFilter implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(ClassFilter.class.getName()); - // ************************************************************************* - // fields - - /** - * class to select (may be null) - */ - final private Class clazz; - // ************************************************************************* - // constructors - - /** - * Instantiate a filter for the specified class. - * - * @param clazz the class to select - */ - public ClassFilter(Class clazz) { - this.clazz = clazz; - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object should be displayed/dumped. - * - * @param physicsObject the joint or collision object to test (unaffected) - * @return return true if physicsObject should be displayed/dumped, false if - * it shouldn't be - */ - @Override - public boolean displayObject(Object physicsObject) { - if (clazz.isAssignableFrom(physicsObject.getClass())) { - return true; - } else { - return false; - } - } -} +/* + Copyright (c) 2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.debug.BulletDebugAppState; +import java.util.logging.Logger; + +/** + * A simple DebugAppStateFilter to select objects that are assignable to a + * specific class. Instances are immutable. + * + * @author Stephen Gold sgold@sonic.net + */ +public class ClassFilter implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(ClassFilter.class.getName()); + // ************************************************************************* + // fields + + /** + * class to select (may be null) + */ + final private Class clazz; + // ************************************************************************* + // constructors + + /** + * Instantiate a filter for the specified class. + * + * @param clazz the class to select + */ + public ClassFilter(Class clazz) { + this.clazz = clazz; + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object should be displayed/dumped. + * + * @param physicsObject the joint or collision object to test (unaffected) + * @return return true if physicsObject should be displayed/dumped, false if + * it shouldn't be + */ + @Override + public boolean displayObject(Object physicsObject) { + if (clazz.isAssignableFrom(physicsObject.getClass())) { + return true; + } else { + return false; + } + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/DacUserFilter.java b/MinieLibrary/src/main/java/jme3utilities/minie/DacUserFilter.java index b61a45a1d..d8bf5ed6b 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/DacUserFilter.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/DacUserFilter.java @@ -1,112 +1,112 @@ -/* - Copyright (c) 2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.animation.DacLinks; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsBody; -import java.util.logging.Logger; - -/** - * A simple DebugAppStateFilter that selects physics objects associated with a - * specific DynamicAnimControl. Instances are immutable. - * - * @author Stephen Gold sgold@sonic.net - */ -public class DacUserFilter implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(DacUserFilter.class.getName()); - // ************************************************************************* - // fields - - /** - * DynamicAnimControl (may be null) - */ - final private DacLinks dac; - // ************************************************************************* - // constructors - - /** - * Instantiate a filter for the specified DynamicAnimControl. - * - * @param dac the desired DynamicAnimControl, or null to display/dump - * objects with no DynamicAnimControl - */ - public DacUserFilter(DacLinks dac) { - this.dac = dac; - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object should be displayed/dumped. - * Note: recursive! - * - * @param physicsObject the joint or collision object to test (unaffected) - * @return return true if the object should be displayed/dumped, false if it - * shouldn't be - */ - @Override - public boolean displayObject(Object physicsObject) { - boolean result = false; - - if (physicsObject instanceof PhysicsCollisionObject) { - PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; - Object user = pco.getUserObject(); - if (user instanceof PhysicsLink) { - PhysicsLink link = (PhysicsLink) user; - if (link.getControl() == dac) { - result = true; - } - } - - } else if (physicsObject instanceof PhysicsJoint) { - PhysicsJoint joint = (PhysicsJoint) physicsObject; - PhysicsBody a = joint.getBody(JointEnd.A); - if (displayObject(a)) { - result = true; - } else { - PhysicsBody b = joint.getBody(JointEnd.B); - if (displayObject(b)) { - result = true; - } - } - } - - return result; - } -} +/* + Copyright (c) 2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.animation.DacLinks; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsBody; +import java.util.logging.Logger; + +/** + * A simple DebugAppStateFilter that selects physics objects associated with a + * specific DynamicAnimControl. Instances are immutable. + * + * @author Stephen Gold sgold@sonic.net + */ +public class DacUserFilter implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(DacUserFilter.class.getName()); + // ************************************************************************* + // fields + + /** + * DynamicAnimControl (may be null) + */ + final private DacLinks dac; + // ************************************************************************* + // constructors + + /** + * Instantiate a filter for the specified DynamicAnimControl. + * + * @param dac the desired DynamicAnimControl, or null to display/dump + * objects with no DynamicAnimControl + */ + public DacUserFilter(DacLinks dac) { + this.dac = dac; + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object should be displayed/dumped. + * Note: recursive! + * + * @param physicsObject the joint or collision object to test (unaffected) + * @return return true if the object should be displayed/dumped, false if it + * shouldn't be + */ + @Override + public boolean displayObject(Object physicsObject) { + boolean result = false; + + if (physicsObject instanceof PhysicsCollisionObject) { + PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; + Object user = pco.getUserObject(); + if (user instanceof PhysicsLink) { + PhysicsLink link = (PhysicsLink) user; + if (link.getControl() == dac) { + result = true; + } + } + + } else if (physicsObject instanceof PhysicsJoint) { + PhysicsJoint joint = (PhysicsJoint) physicsObject; + PhysicsBody a = joint.getBody(JointEnd.A); + if (displayObject(a)) { + result = true; + } else { + PhysicsBody b = joint.getBody(JointEnd.B); + if (displayObject(b)) { + result = true; + } + } + } + + return result; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/DumpFlags.java b/MinieLibrary/src/main/java/jme3utilities/minie/DumpFlags.java index 16fac8072..18948589f 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/DumpFlags.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/DumpFlags.java @@ -1,114 +1,114 @@ -/* - Copyright (c) 2019-2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -/** - * Enumerate the flags used to configure a PhysicsDumper. - * - * @author Stephen Gold sgold@sonic.net - */ -public enum DumpFlags { - // ************************************************************************* - // values - - /** - * world bounds in spatials - */ - BoundsInSpatials, - /** - * render buckets in spatials - */ - Buckets, - /** - * children in compound collision shapes - */ - ChildShapes, - /** - * clusters in soft bodies - */ - ClustersInSofts, - /** - * cull hints in spatials - */ - CullHints, - /** - * ignored objects in collision objects - */ - Ignores, - /** - * physics joints in rigid bodies - */ - JointsInBodies, - /** - * joints in physics spaces - */ - JointsInSpaces, - /** - * parameters in materials - */ - MatParams, - /** - * motors in physics joints - */ - Motors, - /** - * soft-body nodes in clusters - */ - NodesInClusters, - /** - * native IDs of physics objects - */ - NativeIDs, - /** - * nodes in soft bodies - */ - NodesInSofts, - /** - * material-parameter overrides in spatials - */ - Overrides, - /** - * collision objects in physics spaces - */ - Pcos, - /** - * shadow modes in spatials - */ - ShadowModes, - /** - * transforms in spatials - */ - Transforms, - /** - * user data in spatials - */ - UserData, - /** - * vertex data in geometries - */ - VertexData -} +/* + Copyright (c) 2019-2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +/** + * Enumerate the flags used to configure a PhysicsDumper. + * + * @author Stephen Gold sgold@sonic.net + */ +public enum DumpFlags { + // ************************************************************************* + // values + + /** + * world bounds in spatials + */ + BoundsInSpatials, + /** + * render buckets in spatials + */ + Buckets, + /** + * children in compound collision shapes + */ + ChildShapes, + /** + * clusters in soft bodies + */ + ClustersInSofts, + /** + * cull hints in spatials + */ + CullHints, + /** + * ignored objects in collision objects + */ + Ignores, + /** + * physics joints in rigid bodies + */ + JointsInBodies, + /** + * joints in physics spaces + */ + JointsInSpaces, + /** + * parameters in materials + */ + MatParams, + /** + * motors in physics joints + */ + Motors, + /** + * soft-body nodes in clusters + */ + NodesInClusters, + /** + * native IDs of physics objects + */ + NativeIDs, + /** + * nodes in soft bodies + */ + NodesInSofts, + /** + * material-parameter overrides in spatials + */ + Overrides, + /** + * collision objects in physics spaces + */ + Pcos, + /** + * shadow modes in spatials + */ + ShadowModes, + /** + * transforms in spatials + */ + Transforms, + /** + * user data in spatials + */ + UserData, + /** + * vertex data in geometries + */ + VertexData +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/MinieVersion.java b/MinieLibrary/src/main/java/jme3utilities/minie/MinieVersion.java index 6d035fdef..26c1dbfd1 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/MinieVersion.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/MinieVersion.java @@ -1,64 +1,64 @@ -/* - Copyright (c) 2018-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import java.util.logging.Logger; - -/** - * Version strings for the Minie library. All methods should be static. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MinieVersion { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(MinieVersion.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MinieVersion() { - } - // ************************************************************************* - // new methods exposed - - /** - * Return the terse version string for this library. - * - * @return the branch name and revision string (not null, not empty) - */ - public static String versionShort() { - return "master 7.7.1-SNAPSHOT"; - } -} +/* + Copyright (c) 2018-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import java.util.logging.Logger; + +/** + * Version strings for the Minie library. All methods should be static. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MinieVersion { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(MinieVersion.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MinieVersion() { + } + // ************************************************************************* + // new methods exposed + + /** + * Return the terse version string for this library. + * + * @return the branch name and revision string (not null, not empty) + */ + public static String versionShort() { + return "master 7.7.1-SNAPSHOT"; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/MyControlP.java b/MinieLibrary/src/main/java/jme3utilities/minie/MyControlP.java index 46e7195ee..50fbf894e 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/MyControlP.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/MyControlP.java @@ -1,418 +1,418 @@ -/* - Copyright (c) 2013-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.control.AbstractPhysicsControl; -import com.jme3.bullet.control.GhostControl; -import com.jme3.bullet.control.PhysicsControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.control.VehicleControl; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.Control; -import java.util.List; -import java.util.logging.Logger; -import jme3utilities.MyControl; -import jme3utilities.Validate; - -/** - * Utility methods that operate on scene-graph controls/spatials/subtrees that - * may include physics controls. - * - * @see jme3utilities.MyControl - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MyControlP { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(MyControlP.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MyControlP() { - } - // ************************************************************************* - // new methods exposed - - /** - * Check whether a scene-graph control implements applyPhysicsLocal(). - * - * @param sgc the Control to test (may be null, unaffected) - * @return true if it's implemented, otherwise false - */ - public static boolean canApplyPhysicsLocal(Control sgc) { - boolean result = sgc instanceof AbstractPhysicsControl - || sgc instanceof GhostControl - || sgc instanceof RigidBodyControl - || sgc instanceof VehicleControl; - if (sgc instanceof DynamicAnimControl) { - result = false; - } - - return result; - } - - /** - * Check whether a scene-graph control implements isEnabled() and - * setEnabled(). - * - * @param sgc the Control to test (may be null, unaffected) - * @return true if it's implemented, otherwise false - */ - public static boolean canDisable(Control sgc) { - boolean result = MyControl.canDisable(sgc) - || sgc instanceof PhysicsControl; - - return result; - } - - /** - * Generate a textual description of a scene-graph control. - * - * @param sgc instance to describe (not null, unaffected) - * @return description (not null, not empty) - */ - public static String describe(Control sgc) { - String result; - - if (sgc instanceof RigidBodyControl) { - StringBuilder builder = new StringBuilder(60); - - String type = MyControl.describeType(sgc); - builder.append(type); - - RigidBodyControl rigidBodyControl = (RigidBodyControl) sgc; - builder.append('['); - - String desc = MyPco.describe(rigidBodyControl); - builder.append(desc); - - builder.append(' '); - - if (!rigidBodyControl.isInWorld()) { - builder.append("NOT"); - } - builder.append("inWorld,"); - - if (!rigidBodyControl.isActive()) { - builder.append("NOT"); - } - builder.append("active,"); - - if (!rigidBodyControl.isApplyScale()) { - builder.append("NOT"); - } - builder.append("applyScale,"); - - if (!rigidBodyControl.isApplyPhysicsLocal()) { - builder.append("NOT"); - } - builder.append("applyLocal]"); - - result = builder.toString(); - - } else if (sgc instanceof DynamicAnimControl) { - StringBuilder builder = new StringBuilder(60); - - String type = MyControl.describeType(sgc); - builder.append(type); - - DynamicAnimControl dac = (DynamicAnimControl) sgc; - builder.append('['); - int numLinks = dac.countLinks(); - builder.append(numLinks); - builder.append(']'); - - result = builder.toString(); - - } else if (sgc instanceof GhostControl) { - StringBuilder builder = new StringBuilder(60); - - String type = MyControl.describeType(sgc); - builder.append(type); - - GhostControl ghostControl = (GhostControl) sgc; - builder.append('['); - - if (!ghostControl.isApplyScale()) { - builder.append("NOT"); - } - builder.append("applyScale,"); - - if (!ghostControl.isApplyPhysicsLocal()) { - builder.append("NOT"); - } - builder.append("applyLocal]"); - - result = builder.toString(); - - } else { - result = MyControl.describe(sgc); - } - - return result; - } - - /** - * Disable all physics controls added to the specified subtree of the scene - * graph. Disabling these controls removes any collision objects they may - * have added to physics spaces. Note: recursive! - * - * @param subtree (not null) - */ - public static void disablePhysicsControls(Spatial subtree) { - int numControls = subtree.getNumControls(); - for (int controlI = 0; controlI < numControls; ++controlI) { - Control control = subtree.getControl(controlI); - if (control instanceof PhysicsControl) { - setEnabled(control, false); - } - } - if (subtree instanceof Node) { - Node node = (Node) subtree; - List children = node.getChildren(); - for (Spatial child : children) { - disablePhysicsControls(child); - } - } - } - - /** - * Enable all physics controls added to the specified subtree of the scene - * graph and configure their physics spaces. Note: recursive! - * - * @param subtree (not null) - * @param space the PhysicsSpace to add to, or null for none - */ - public static void enablePhysicsControls( - Spatial subtree, PhysicsSpace space) { - int numControls = subtree.getNumControls(); - for (int controlI = 0; controlI < numControls; ++controlI) { - Control control = subtree.getControl(controlI); - if (control instanceof PhysicsControl) { - PhysicsControl pc = (PhysicsControl) control; - pc.setPhysicsSpace(space); - pc.setEnabled(true); - } - } - if (subtree instanceof Node) { - Node node = (Node) subtree; - List children = node.getChildren(); - for (Spatial child : children) { - enablePhysicsControls(child, space); - } - } - } - - /** - * Access the first enabled RigidBodyControl added to a Spatial. - * - * @param spatial spatial to search (not null, unaffected) - * @return the pre-existing control, or null if none found - */ - public static RigidBodyControl findEnabledRbc(Spatial spatial) { - RigidBodyControl result = null; - int numControls = spatial.getNumControls(); - for (int controlI = 0; controlI < numControls; ++controlI) { - Control control = spatial.getControl(controlI); - if (control instanceof RigidBodyControl) { - RigidBodyControl rbc = (RigidBodyControl) control; - if (rbc.isEnabled()) { - result = rbc; - break; - } - } - } - - return result; - } - - /** - * Test whether the specified SGC applies physics coordinates to its - * spatial's local translation. - * - * @param sgc which scene-graph control (may be null, unaffected) - * @return true if applied to local translation, otherwise false - */ - public static boolean isApplyPhysicsLocal(Control sgc) { - Validate.nonNull(sgc, "control"); - - boolean result; - if (sgc instanceof AbstractPhysicsControl) { - AbstractPhysicsControl apc = (AbstractPhysicsControl) sgc; - result = apc.isApplyPhysicsLocal(); - - } else if (sgc instanceof GhostControl) { - GhostControl gc = (GhostControl) sgc; - result = gc.isApplyPhysicsLocal(); - - } else if (sgc instanceof RigidBodyControl) { - RigidBodyControl rbc = (RigidBodyControl) sgc; - result = rbc.isApplyPhysicsLocal(); - - } else if (sgc instanceof VehicleControl) { - VehicleControl vc = (VehicleControl) sgc; - result = vc.isApplyPhysicsLocal(); - - } else { - String typeName = sgc.getClass().getCanonicalName(); - String message = typeName + " does not support local physics."; - throw new IllegalArgumentException(message); - } - - return result; - } - - /** - * Test whether a scene-graph control is enabled. - * - * @param sgc control to test (not null, unaffected) - * @return true if the control is enabled, otherwise false - */ - public static boolean isEnabled(Control sgc) { - Validate.nonNull(sgc, "control"); - - boolean result; - if (sgc instanceof PhysicsControl) { - PhysicsControl physicsControl = (PhysicsControl) sgc; - result = physicsControl.isEnabled(); - } else { - result = MyControl.isEnabled(sgc); - } - - return result; - } - - /** - * Test whether a Spatial is physics-controlled. - * - * @param spatial spatial to test (not null, unaffected) - * @return true if the spatial is controlled by physics, otherwise false - */ - public static boolean isPhysical(Spatial spatial) { - Object rigidBodyControl = spatial.getControl(RigidBodyControl.class); - boolean result = rigidBodyControl != null; - - return result; - } - - /** - * Read a spatial's mass. - * - * @param spatial which spatial to measure (not null, unaffected) - * @return mass (>0) or zero for a static object - */ - public static float mass(Spatial spatial) { - Validate.nonNull(spatial, "spatial"); - - RigidBodyControl rigidBodyControl = findEnabledRbc(spatial); - float mass = rigidBodyControl.getMass(); - - assert mass >= 0f : mass; - return mass; - } - - /** - * Remove all non-physics controls from the specified subtree of the scene - * graph. Note: recursive! - * - * @param subtree (not null) - */ - public static void removeNonPhysicsControls(Spatial subtree) { - int numControls = subtree.getNumControls(); - for (int controlI = numControls - 1; controlI >= 0; --controlI) { - Control control = subtree.getControl(controlI); - if (!(control instanceof PhysicsControl)) { - subtree.removeControl(control); - } - } - if (subtree instanceof Node) { - Node node = (Node) subtree; - List children = node.getChildren(); - for (Spatial child : children) { - removeNonPhysicsControls(child); - } - } - } - - /** - * Alter whether the specified SGC applies physics coordinates to its - * spatial's local translation. - * - * @param sgc control to alter (not null) - * @param newSetting true means enable the control, false means disable it - */ - public static void setApplyPhysicsLocal(Control sgc, boolean newSetting) { - if (sgc instanceof AbstractPhysicsControl) { - AbstractPhysicsControl apc = (AbstractPhysicsControl) sgc; - apc.setApplyPhysicsLocal(newSetting); - - } else if (sgc instanceof GhostControl) { - GhostControl gc = (GhostControl) sgc; - gc.setApplyPhysicsLocal(newSetting); - - } else if (sgc instanceof RigidBodyControl) { - RigidBodyControl rbc = (RigidBodyControl) sgc; - rbc.setApplyPhysicsLocal(newSetting); - - } else if (sgc instanceof VehicleControl) { - VehicleControl vc = (VehicleControl) sgc; - vc.setApplyPhysicsLocal(newSetting); - - } else { - String typeName = sgc.getClass().getCanonicalName(); - String msg = typeName + " does not support local physics."; - throw new IllegalArgumentException(msg); - } - } - - /** - * Alter the enabled state of a scene-graph control. - * - * @param sgc control to alter (not null) - * @param newState true means enable the control, false means disable it - */ - public static void setEnabled(Control sgc, boolean newState) { - if (sgc instanceof PhysicsControl) { - PhysicsControl physicsControl = (PhysicsControl) sgc; - physicsControl.setEnabled(newState); - } else { - MyControl.setEnabled(sgc, newState); - } - } -} +/* + Copyright (c) 2013-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.control.AbstractPhysicsControl; +import com.jme3.bullet.control.GhostControl; +import com.jme3.bullet.control.PhysicsControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.control.VehicleControl; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import java.util.List; +import java.util.logging.Logger; +import jme3utilities.MyControl; +import jme3utilities.Validate; + +/** + * Utility methods that operate on scene-graph controls/spatials/subtrees that + * may include physics controls. + * + * @see jme3utilities.MyControl + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MyControlP { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(MyControlP.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MyControlP() { + } + // ************************************************************************* + // new methods exposed + + /** + * Check whether a scene-graph control implements applyPhysicsLocal(). + * + * @param sgc the Control to test (may be null, unaffected) + * @return true if it's implemented, otherwise false + */ + public static boolean canApplyPhysicsLocal(Control sgc) { + boolean result = sgc instanceof AbstractPhysicsControl + || sgc instanceof GhostControl + || sgc instanceof RigidBodyControl + || sgc instanceof VehicleControl; + if (sgc instanceof DynamicAnimControl) { + result = false; + } + + return result; + } + + /** + * Check whether a scene-graph control implements isEnabled() and + * setEnabled(). + * + * @param sgc the Control to test (may be null, unaffected) + * @return true if it's implemented, otherwise false + */ + public static boolean canDisable(Control sgc) { + boolean result = MyControl.canDisable(sgc) + || sgc instanceof PhysicsControl; + + return result; + } + + /** + * Generate a textual description of a scene-graph control. + * + * @param sgc instance to describe (not null, unaffected) + * @return description (not null, not empty) + */ + public static String describe(Control sgc) { + String result; + + if (sgc instanceof RigidBodyControl) { + StringBuilder builder = new StringBuilder(60); + + String type = MyControl.describeType(sgc); + builder.append(type); + + RigidBodyControl rigidBodyControl = (RigidBodyControl) sgc; + builder.append('['); + + String desc = MyPco.describe(rigidBodyControl); + builder.append(desc); + + builder.append(' '); + + if (!rigidBodyControl.isInWorld()) { + builder.append("NOT"); + } + builder.append("inWorld,"); + + if (!rigidBodyControl.isActive()) { + builder.append("NOT"); + } + builder.append("active,"); + + if (!rigidBodyControl.isApplyScale()) { + builder.append("NOT"); + } + builder.append("applyScale,"); + + if (!rigidBodyControl.isApplyPhysicsLocal()) { + builder.append("NOT"); + } + builder.append("applyLocal]"); + + result = builder.toString(); + + } else if (sgc instanceof DynamicAnimControl) { + StringBuilder builder = new StringBuilder(60); + + String type = MyControl.describeType(sgc); + builder.append(type); + + DynamicAnimControl dac = (DynamicAnimControl) sgc; + builder.append('['); + int numLinks = dac.countLinks(); + builder.append(numLinks); + builder.append(']'); + + result = builder.toString(); + + } else if (sgc instanceof GhostControl) { + StringBuilder builder = new StringBuilder(60); + + String type = MyControl.describeType(sgc); + builder.append(type); + + GhostControl ghostControl = (GhostControl) sgc; + builder.append('['); + + if (!ghostControl.isApplyScale()) { + builder.append("NOT"); + } + builder.append("applyScale,"); + + if (!ghostControl.isApplyPhysicsLocal()) { + builder.append("NOT"); + } + builder.append("applyLocal]"); + + result = builder.toString(); + + } else { + result = MyControl.describe(sgc); + } + + return result; + } + + /** + * Disable all physics controls added to the specified subtree of the scene + * graph. Disabling these controls removes any collision objects they may + * have added to physics spaces. Note: recursive! + * + * @param subtree (not null) + */ + public static void disablePhysicsControls(Spatial subtree) { + int numControls = subtree.getNumControls(); + for (int controlI = 0; controlI < numControls; ++controlI) { + Control control = subtree.getControl(controlI); + if (control instanceof PhysicsControl) { + setEnabled(control, false); + } + } + if (subtree instanceof Node) { + Node node = (Node) subtree; + List children = node.getChildren(); + for (Spatial child : children) { + disablePhysicsControls(child); + } + } + } + + /** + * Enable all physics controls added to the specified subtree of the scene + * graph and configure their physics spaces. Note: recursive! + * + * @param subtree (not null) + * @param space the PhysicsSpace to add to, or null for none + */ + public static void enablePhysicsControls( + Spatial subtree, PhysicsSpace space) { + int numControls = subtree.getNumControls(); + for (int controlI = 0; controlI < numControls; ++controlI) { + Control control = subtree.getControl(controlI); + if (control instanceof PhysicsControl) { + PhysicsControl pc = (PhysicsControl) control; + pc.setPhysicsSpace(space); + pc.setEnabled(true); + } + } + if (subtree instanceof Node) { + Node node = (Node) subtree; + List children = node.getChildren(); + for (Spatial child : children) { + enablePhysicsControls(child, space); + } + } + } + + /** + * Access the first enabled RigidBodyControl added to a Spatial. + * + * @param spatial spatial to search (not null, unaffected) + * @return the pre-existing control, or null if none found + */ + public static RigidBodyControl findEnabledRbc(Spatial spatial) { + RigidBodyControl result = null; + int numControls = spatial.getNumControls(); + for (int controlI = 0; controlI < numControls; ++controlI) { + Control control = spatial.getControl(controlI); + if (control instanceof RigidBodyControl) { + RigidBodyControl rbc = (RigidBodyControl) control; + if (rbc.isEnabled()) { + result = rbc; + break; + } + } + } + + return result; + } + + /** + * Test whether the specified SGC applies physics coordinates to its + * spatial's local translation. + * + * @param sgc which scene-graph control (may be null, unaffected) + * @return true if applied to local translation, otherwise false + */ + public static boolean isApplyPhysicsLocal(Control sgc) { + Validate.nonNull(sgc, "control"); + + boolean result; + if (sgc instanceof AbstractPhysicsControl) { + AbstractPhysicsControl apc = (AbstractPhysicsControl) sgc; + result = apc.isApplyPhysicsLocal(); + + } else if (sgc instanceof GhostControl) { + GhostControl gc = (GhostControl) sgc; + result = gc.isApplyPhysicsLocal(); + + } else if (sgc instanceof RigidBodyControl) { + RigidBodyControl rbc = (RigidBodyControl) sgc; + result = rbc.isApplyPhysicsLocal(); + + } else if (sgc instanceof VehicleControl) { + VehicleControl vc = (VehicleControl) sgc; + result = vc.isApplyPhysicsLocal(); + + } else { + String typeName = sgc.getClass().getCanonicalName(); + String message = typeName + " does not support local physics."; + throw new IllegalArgumentException(message); + } + + return result; + } + + /** + * Test whether a scene-graph control is enabled. + * + * @param sgc control to test (not null, unaffected) + * @return true if the control is enabled, otherwise false + */ + public static boolean isEnabled(Control sgc) { + Validate.nonNull(sgc, "control"); + + boolean result; + if (sgc instanceof PhysicsControl) { + PhysicsControl physicsControl = (PhysicsControl) sgc; + result = physicsControl.isEnabled(); + } else { + result = MyControl.isEnabled(sgc); + } + + return result; + } + + /** + * Test whether a Spatial is physics-controlled. + * + * @param spatial spatial to test (not null, unaffected) + * @return true if the spatial is controlled by physics, otherwise false + */ + public static boolean isPhysical(Spatial spatial) { + Object rigidBodyControl = spatial.getControl(RigidBodyControl.class); + boolean result = rigidBodyControl != null; + + return result; + } + + /** + * Read a spatial's mass. + * + * @param spatial which spatial to measure (not null, unaffected) + * @return mass (>0) or zero for a static object + */ + public static float mass(Spatial spatial) { + Validate.nonNull(spatial, "spatial"); + + RigidBodyControl rigidBodyControl = findEnabledRbc(spatial); + float mass = rigidBodyControl.getMass(); + + assert mass >= 0f : mass; + return mass; + } + + /** + * Remove all non-physics controls from the specified subtree of the scene + * graph. Note: recursive! + * + * @param subtree (not null) + */ + public static void removeNonPhysicsControls(Spatial subtree) { + int numControls = subtree.getNumControls(); + for (int controlI = numControls - 1; controlI >= 0; --controlI) { + Control control = subtree.getControl(controlI); + if (!(control instanceof PhysicsControl)) { + subtree.removeControl(control); + } + } + if (subtree instanceof Node) { + Node node = (Node) subtree; + List children = node.getChildren(); + for (Spatial child : children) { + removeNonPhysicsControls(child); + } + } + } + + /** + * Alter whether the specified SGC applies physics coordinates to its + * spatial's local translation. + * + * @param sgc control to alter (not null) + * @param newSetting true means enable the control, false means disable it + */ + public static void setApplyPhysicsLocal(Control sgc, boolean newSetting) { + if (sgc instanceof AbstractPhysicsControl) { + AbstractPhysicsControl apc = (AbstractPhysicsControl) sgc; + apc.setApplyPhysicsLocal(newSetting); + + } else if (sgc instanceof GhostControl) { + GhostControl gc = (GhostControl) sgc; + gc.setApplyPhysicsLocal(newSetting); + + } else if (sgc instanceof RigidBodyControl) { + RigidBodyControl rbc = (RigidBodyControl) sgc; + rbc.setApplyPhysicsLocal(newSetting); + + } else if (sgc instanceof VehicleControl) { + VehicleControl vc = (VehicleControl) sgc; + vc.setApplyPhysicsLocal(newSetting); + + } else { + String typeName = sgc.getClass().getCanonicalName(); + String msg = typeName + " does not support local physics."; + throw new IllegalArgumentException(msg); + } + } + + /** + * Alter the enabled state of a scene-graph control. + * + * @param sgc control to alter (not null) + * @param newState true means enable the control, false means disable it + */ + public static void setEnabled(Control sgc, boolean newState) { + if (sgc instanceof PhysicsControl) { + PhysicsControl physicsControl = (PhysicsControl) sgc; + physicsControl.setEnabled(newState); + } else { + MyControl.setEnabled(sgc, newState); + } + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/MyPco.java b/MinieLibrary/src/main/java/jme3utilities/minie/MyPco.java index 8d0308b93..28efce217 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/MyPco.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/MyPco.java @@ -1,140 +1,140 @@ -/* - Copyright (c) 2017-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.objects.PhysicsCharacter; -import com.jme3.bullet.objects.PhysicsGhostObject; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsVehicle; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; - -/** - * Utility methods that operate on physics collision objects. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MyPco { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger = Logger.getLogger(MyPco.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MyPco() { - } - // ************************************************************************* - // new methods exposed - - /** - * Briefly describe a rigid body for MyControlP or PhysicsDumper. TODO add a - * similar method for PhysicsCharacter - * - * @param body (not null, unaffected) - * @return a descriptive string (not null, not empty) - */ - public static String describe(PhysicsRigidBody body) { - String result; - if (body.isStatic()) { - result = "Sta"; - } else if (body.isKinematic()) { - result = "Kin"; - } else { - float mass = body.getMass(); - String massText = MyString.describe(mass); - String activeText = body.isActive() ? "" : "/inactive"; - result = String.format("Dyn(mass=%s)%s", massText, activeText); - } - - if (!body.isContactResponse()) { - result += "/NOresponse"; - } - if (!body.isInWorld()) { - result += "/NOspace"; - } - - return result; - } - - /** - * Generate a name for the specified collision object. - * - * @param pco object to name (not null, unaffected) - * @return the name (not null, not empty) - */ - public static String objectName(PhysicsCollisionObject pco) { - Validate.nonNull(pco, "physics object"); - - long id = pco.nativeId(); - String name; - if (pco instanceof PhysicsCharacter) { - name = String.format("chara%d", id); - } else if (pco instanceof PhysicsGhostObject) { - name = String.format("ghost%d", id); - } else if (pco instanceof PhysicsVehicle) { - // must test before RigidBody - name = String.format("vehic%d", id); - } else if (pco instanceof PhysicsRigidBody) { - name = String.format("rigid%d", id); - } else { - String typeName = pco.getClass().getCanonicalName(); - String msg = "Unknown type of collision object: " + typeName; - throw new IllegalArgumentException(msg); - } - - return name; - } - - /** - * Parse the ID of a collision object from its name. - * - * @param name the input text (not null, length>5) - * @return the object's ID - * - * @see #objectName(com.jme3.bullet.collision.PhysicsCollisionObject) - */ - public static long parseId(String name) { - Validate.nonEmpty(name, "name"); - - if (name.length() <= 5) { - throw new IllegalArgumentException("name=" + MyString.quote(name)); - } - String decimal = name.substring(5); - long result = Long.parseLong(decimal); - - return result; - } -} +/* + Copyright (c) 2017-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; + +/** + * Utility methods that operate on physics collision objects. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MyPco { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger = Logger.getLogger(MyPco.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MyPco() { + } + // ************************************************************************* + // new methods exposed + + /** + * Briefly describe a rigid body for MyControlP or PhysicsDumper. TODO add a + * similar method for PhysicsCharacter + * + * @param body (not null, unaffected) + * @return a descriptive string (not null, not empty) + */ + public static String describe(PhysicsRigidBody body) { + String result; + if (body.isStatic()) { + result = "Sta"; + } else if (body.isKinematic()) { + result = "Kin"; + } else { + float mass = body.getMass(); + String massText = MyString.describe(mass); + String activeText = body.isActive() ? "" : "/inactive"; + result = String.format("Dyn(mass=%s)%s", massText, activeText); + } + + if (!body.isContactResponse()) { + result += "/NOresponse"; + } + if (!body.isInWorld()) { + result += "/NOspace"; + } + + return result; + } + + /** + * Generate a name for the specified collision object. + * + * @param pco object to name (not null, unaffected) + * @return the name (not null, not empty) + */ + public static String objectName(PhysicsCollisionObject pco) { + Validate.nonNull(pco, "physics object"); + + long id = pco.nativeId(); + String name; + if (pco instanceof PhysicsCharacter) { + name = String.format("chara%d", id); + } else if (pco instanceof PhysicsGhostObject) { + name = String.format("ghost%d", id); + } else if (pco instanceof PhysicsVehicle) { + // must test before RigidBody + name = String.format("vehic%d", id); + } else if (pco instanceof PhysicsRigidBody) { + name = String.format("rigid%d", id); + } else { + String typeName = pco.getClass().getCanonicalName(); + String msg = "Unknown type of collision object: " + typeName; + throw new IllegalArgumentException(msg); + } + + return name; + } + + /** + * Parse the ID of a collision object from its name. + * + * @param name the input text (not null, length>5) + * @return the object's ID + * + * @see #objectName(com.jme3.bullet.collision.PhysicsCollisionObject) + */ + public static long parseId(String name) { + Validate.nonEmpty(name, "name"); + + if (name.length() <= 5) { + throw new IllegalArgumentException("name=" + MyString.quote(name)); + } + String decimal = name.substring(5); + long result = Long.parseLong(decimal); + + return result; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/MyShape.java b/MinieLibrary/src/main/java/jme3utilities/minie/MyShape.java index 1a1bb1f01..231ab86e6 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/MyShape.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/MyShape.java @@ -1,545 +1,545 @@ -/* - Copyright (c) 2014-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.collision.shapes.SimplexCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.math.Vector3f; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.math.MyVector3f; - -/** - * Utility methods for physics collision shapes. All methods should be static. - * - * @author Stephen Gold sgold@sonic.net - */ -final public class MyShape { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(MyShape.class.getName()); - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private MyShape() { - } - // ************************************************************************* - // new methods exposed - - /** - * Describe the type of the specified shape. - * - * @param shape the shape to describe (not null, unaffected) - * @return the type description (not null) - */ - public static String describeType(CollisionShape shape) { - String description = shape.getClass().getSimpleName(); - if (description.endsWith("Shape")) { - description = MyString.removeSuffix(description, "Shape"); - } - if (description.endsWith("Collision")) { - description = MyString.removeSuffix(description, "Collision"); - } - - return description; - } - - /** - * Determine the unscaled half extents of the specified shape. - * - * @param shape (not null, unaffected) - * @param storeResult storage for the result (modified if not null) - * @return a vector with all components non-negative (either storeResult or - * a new instance) - */ - public static Vector3f halfExtents(CollisionShape shape, - Vector3f storeResult) { - Validate.nonNull(shape, "shape"); - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - - if (shape instanceof BoxCollisionShape) { - BoxCollisionShape box = (BoxCollisionShape) shape; - box.getHalfExtents(result); - - } else if (shape instanceof CapsuleCollisionShape) { - CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; - float height = capsule.getHeight(); - float radius = capsule.getRadius(); - float axisHalfExtent = height / 2f + radius; - int axisIndex = capsule.getAxis(); - switch (axisIndex) { - case PhysicsSpace.AXIS_X: - result.set(axisHalfExtent, radius, radius); - break; - case PhysicsSpace.AXIS_Y: - result.set(radius, axisHalfExtent, radius); - break; - case PhysicsSpace.AXIS_Z: - result.set(radius, radius, axisHalfExtent); - break; - default: - String message = "axisIndex = " + axisIndex; - throw new IllegalArgumentException(message); - } - - } else if (shape instanceof ConeCollisionShape) { - ConeCollisionShape cone = (ConeCollisionShape) shape; - float height = cone.getHeight(); - float radius = cone.getRadius(); - float axisHalfExtent = height / 2f; - int axisIndex = cone.getAxis(); - switch (axisIndex) { - case PhysicsSpace.AXIS_X: - result.set(axisHalfExtent, radius, radius); - break; - case PhysicsSpace.AXIS_Y: - result.set(radius, axisHalfExtent, radius); - break; - case PhysicsSpace.AXIS_Z: - result.set(radius, radius, axisHalfExtent); - break; - default: - String message = "axisIndex = " + axisIndex; - throw new IllegalArgumentException(message); - } - - } else if (shape instanceof CylinderCollisionShape) { - CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; - cylinder.getHalfExtents(result); - - } else if (shape instanceof HullCollisionShape) { - HullCollisionShape hull = (HullCollisionShape) shape; - hull.getHalfExtents(result); - - } else if (shape instanceof MultiSphere) { - MultiSphere multiSphere = (MultiSphere) shape; - if (multiSphere.countSpheres() == 1) { - float radius = multiSphere.getRadius(0); - result.set(radius, radius, radius); - } - - } else if (shape instanceof SimplexCollisionShape) { - SimplexCollisionShape simplex = (SimplexCollisionShape) shape; - simplex.getHalfExtents(result); - - } else if (shape instanceof SphereCollisionShape) { - SphereCollisionShape sphere = (SphereCollisionShape) shape; - float radius = sphere.getRadius(); - result.set(radius, radius, radius); - - } else { // TODO handle more shapes - String typeName = shape.getClass().getCanonicalName(); - String message = typeName + " lacks half extents."; - throw new IllegalArgumentException(message); - } - - assert MyVector3f.isAllNonNegative(result) : result; - - return result; - } - - /** - * Determine the unscaled height of the specified shape. - * - * @param shape (not null, unaffected) - * @return the unscaled height (≥0) used to create the shape, or NaN if - * that height is undefined or unknown - */ - public static float height(CollisionShape shape) { - Validate.nonNull(shape, "shape"); - - float result = Float.NaN; - if (shape instanceof CapsuleCollisionShape) { - CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; - result = capsule.getHeight(); - - } else if (shape instanceof ConeCollisionShape) { - ConeCollisionShape cone = (ConeCollisionShape) shape; - result = cone.getHeight(); - - } else if (shape instanceof CylinderCollisionShape) { - CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; - Vector3f halfExtents = cylinder.getHalfExtents(null); - int axisIndex = cylinder.getAxis(); - result = 2f * halfExtents.get(axisIndex); - - } else if (shape instanceof MultiSphere) { - MultiSphere multiSphere = (MultiSphere) shape; - if (multiSphere.countSpheres() == 1) { - result = 2f * multiSphere.getRadius(0); - } - - } else if (shape instanceof SphereCollisionShape) { - SphereCollisionShape sphere = (SphereCollisionShape) shape; - result = 2f * sphere.getRadius(); - } - - assert Float.isNaN(result) || result >= 0f : result; - return result; - } - - /** - * Estimate the volume of each child in a CompoundCollisionShape. - * - * @param shape (not null, unaffected) - * @return a new array of volumes (each ≥0) - */ - public static float[] listVolumes(CompoundCollisionShape shape) { - ChildCollisionShape[] children = shape.listChildren(); - int numChildren = children.length; - float[] result = new float[numChildren]; - for (int i = 0; i < numChildren; ++i) { - CollisionShape baseShape = children[i].getShape(); - result[i] = volume(baseShape); - } - - return result; - } - - /** - * Determine the main axis of the specified shape, provided it's a capsule, - * cone, or cylinder. - * - * @param shape (may be null, unaffected) - * @return 0→X, 1→Y, 2→Z, -1→doesn't have a main axis - */ - public static int mainAxisIndex(CollisionShape shape) { - int result = -1; - if (shape instanceof CapsuleCollisionShape) { - CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; - result = capsule.getAxis(); - - } else if (shape instanceof ConeCollisionShape) { - ConeCollisionShape cone = (ConeCollisionShape) shape; - result = cone.getAxis(); - - } else if (shape instanceof CylinderCollisionShape) { - CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; - result = cylinder.getAxis(); - } - - return result; - } - - /** - * Parse the native ID of a shape from its String representation. - * - * @param string the input text (not null, exactly one "#") - * @return the shape's ID - * - * @see com.jme3.bullet.collision.shapes.CollisionShape#toString() - */ - public static long parseNativeId(String string) { - Validate.nonEmpty(string, "string"); - - String[] parts = string.split("#"); - if (parts.length != 2) { - String message = "string = " + MyString.quote(string); - throw new IllegalArgumentException(message); - } - String hexadecimal = parts[1]; - long result = Long.parseLong(hexadecimal, 16); - - return result; - } - - /** - * Determine the unscaled radius of the specified shape. - * - * @param shape (not null, unaffected) - * @return the unscaled radius (≥0) used to create the shape, or NaN if - * that radius is undefined or unknown - */ - public static float radius(CollisionShape shape) { - Validate.nonNull(shape, "shape"); - - float result = Float.NaN; - if (shape instanceof CapsuleCollisionShape) { - CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; - result = capsule.getRadius(); - - } else if (shape instanceof ConeCollisionShape) { - ConeCollisionShape cone = (ConeCollisionShape) shape; - result = cone.getRadius(); - - } else if (shape instanceof CylinderCollisionShape) { - Vector3f halfExtents = halfExtents(shape, null); - int axisIndex = mainAxisIndex(shape); - float r1; - float r2; - switch (axisIndex) { - case PhysicsSpace.AXIS_X: - r1 = halfExtents.y; - r2 = halfExtents.z; - break; - case PhysicsSpace.AXIS_Y: - r1 = halfExtents.x; - r2 = halfExtents.z; - break; - case PhysicsSpace.AXIS_Z: - r1 = halfExtents.x; - r2 = halfExtents.y; - break; - default: - String message = "axisIndex = " + axisIndex; - throw new IllegalArgumentException(message); - } - if (r1 == r2) { - result = r1; - } - - } else if (shape instanceof MultiSphere) { - MultiSphere multiSphere = (MultiSphere) shape; - if (multiSphere.countSpheres() == 1) { - result = multiSphere.getRadius(0); - } - - } else if (shape instanceof SphereCollisionShape) { - result = ((SphereCollisionShape) shape).getRadius(); - } - - assert Float.isNaN(result) || result >= 0f : result; - return result; - } - - /** - * Copy a shape, altering only its half extents. - * - * @param oldShape input shape (not null, unaffected) - * @param newHalfExtents (not null, no negative component, unaffected) - * @return a new shape, or null if not possible - */ - public static CollisionShape setHalfExtents( - CollisionShape oldShape, Vector3f newHalfExtents) { - Validate.nonNull(oldShape, "old shape"); - Validate.nonNegative(newHalfExtents, "new half extents"); - - CollisionShape result; - if (oldShape instanceof BoxCollisionShape) { - result = new BoxCollisionShape(newHalfExtents); - - } else if (oldShape instanceof CapsuleCollisionShape - || oldShape instanceof ConeCollisionShape) { - int axisIndex = mainAxisIndex(oldShape); - float axisHalfExtent; - float radius1; - float radius2; - switch (axisIndex) { - case PhysicsSpace.AXIS_X: - axisHalfExtent = newHalfExtents.x; - radius1 = newHalfExtents.y; - radius2 = newHalfExtents.z; - break; - case PhysicsSpace.AXIS_Y: - axisHalfExtent = newHalfExtents.y; - radius1 = newHalfExtents.x; - radius2 = newHalfExtents.z; - break; - case PhysicsSpace.AXIS_Z: - axisHalfExtent = newHalfExtents.z; - radius1 = newHalfExtents.x; - radius2 = newHalfExtents.y; - break; - default: - String message = "axisIndex = " + axisIndex; - throw new IllegalArgumentException(message); - } - if (radius1 != radius2) { - result = null; - } else if (oldShape instanceof CapsuleCollisionShape) { - float height = 2f * (axisHalfExtent - radius1); - result = new CapsuleCollisionShape(radius1, height, axisIndex); - } else { - assert oldShape instanceof ConeCollisionShape; - float height = 2f * axisHalfExtent; - result = new ConeCollisionShape(radius1, height, axisIndex); - } - - } else if (oldShape instanceof CylinderCollisionShape) { - int axisIndex = mainAxisIndex(oldShape); - result = new CylinderCollisionShape(newHalfExtents, axisIndex); - - } else if (oldShape instanceof SphereCollisionShape) { - if (!MyVector3f.isScaleUniform(newHalfExtents)) { - result = null; - } else { - result = new SphereCollisionShape(newHalfExtents.x); - } - - } else { - result = null; - } - - if (result != null) { - boolean enable = oldShape.isContactFilterEnabled(); - result.setContactFilterEnabled(enable); - - if (!(result instanceof CapsuleCollisionShape) - && !(result instanceof SphereCollisionShape)) { - float margin = oldShape.getMargin(); - result.setMargin(margin); - } - } - - return result; - } - - /** - * Copy a shape, altering only its height. - * - * @param oldShape input shape (not null, unaffected) - * @param newHeight the desired unscaled height (≥0) - * @return a new shape - */ - public static CollisionShape setHeight( - CollisionShape oldShape, float newHeight) { - Validate.nonNull(oldShape, "old shape"); - Validate.nonNegative(newHeight, "new height"); - - CollisionShape result; - if (oldShape instanceof BoxCollisionShape) { - result = setRadius(oldShape, newHeight / 2f); - - } else if (oldShape instanceof CapsuleCollisionShape) { - float radius = radius(oldShape); - int axisIndex = mainAxisIndex(oldShape); - result = new CapsuleCollisionShape(radius, newHeight, axisIndex); - - } else if (oldShape instanceof ConeCollisionShape) { - float radius = radius(oldShape); - int axisIndex = mainAxisIndex(oldShape); - result = new ConeCollisionShape(radius, newHeight, axisIndex); - - } else if (oldShape instanceof CylinderCollisionShape) { - float radius = radius(oldShape); - int axisIndex = mainAxisIndex(oldShape); - result = new CylinderCollisionShape(radius, newHeight, axisIndex); - - } else if (oldShape instanceof SphereCollisionShape) { - result = setRadius(oldShape, newHeight / 2f); - - } else { - result = null; - } - - if (result != null) { - boolean enable = oldShape.isContactFilterEnabled(); - result.setContactFilterEnabled(enable); - - if (!(result instanceof CapsuleCollisionShape) - && !(result instanceof SphereCollisionShape)) { - float margin = oldShape.getMargin(); - result.setMargin(margin); - } - } - - return result; - } - - /** - * Copy a shape, altering only its radius. - * - * @param oldShape input shape (not null, unaffected) - * @param newRadius the desired unscaled radius (≥0) - * @return a new shape - */ - public static CollisionShape setRadius( - CollisionShape oldShape, float newRadius) { - Validate.nonNull(oldShape, "old shape"); - Validate.nonNegative(newRadius, "new radius"); - - CollisionShape result; - if (oldShape instanceof BoxCollisionShape) { - result = new BoxCollisionShape(newRadius); - - } else if (oldShape instanceof CapsuleCollisionShape) { - int axisIndex = mainAxisIndex(oldShape); - float height = height(oldShape); - result = new CapsuleCollisionShape(newRadius, height, axisIndex); - - } else if (oldShape instanceof ConeCollisionShape) { - int axisIndex = mainAxisIndex(oldShape); - float height = height(oldShape); - result = new ConeCollisionShape(newRadius, height, axisIndex); - - } else if (oldShape instanceof CylinderCollisionShape) { - int axisIndex = mainAxisIndex(oldShape); - float height = height(oldShape); - result = new CylinderCollisionShape(newRadius, height, axisIndex); - - } else if (oldShape instanceof SphereCollisionShape) { - result = new SphereCollisionShape(newRadius); - - } else { - result = null; - } - - if (result != null) { - boolean enable = oldShape.isContactFilterEnabled(); - result.setContactFilterEnabled(enable); - - if (!(result instanceof CapsuleCollisionShape) - && !(result instanceof SphereCollisionShape)) { - float margin = oldShape.getMargin(); - result.setMargin(margin); - } - } - - return result; - } - - /** - * Estimate the scaled volume of a closed shape. - * - * @param shape (not null, unaffected) - * @return the volume (in physics-space units cubed, ≥0) - */ - public static float volume(CollisionShape shape) { - float result = shape.scaledVolume(); - assert result >= 0f : result; - return result; - } -} +/* + Copyright (c) 2014-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.ConeCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.collision.shapes.SimplexCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.math.Vector3f; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.math.MyVector3f; + +/** + * Utility methods for physics collision shapes. All methods should be static. + * + * @author Stephen Gold sgold@sonic.net + */ +final public class MyShape { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(MyShape.class.getName()); + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MyShape() { + } + // ************************************************************************* + // new methods exposed + + /** + * Describe the type of the specified shape. + * + * @param shape the shape to describe (not null, unaffected) + * @return the type description (not null) + */ + public static String describeType(CollisionShape shape) { + String description = shape.getClass().getSimpleName(); + if (description.endsWith("Shape")) { + description = MyString.removeSuffix(description, "Shape"); + } + if (description.endsWith("Collision")) { + description = MyString.removeSuffix(description, "Collision"); + } + + return description; + } + + /** + * Determine the unscaled half extents of the specified shape. + * + * @param shape (not null, unaffected) + * @param storeResult storage for the result (modified if not null) + * @return a vector with all components non-negative (either storeResult or + * a new instance) + */ + public static Vector3f halfExtents(CollisionShape shape, + Vector3f storeResult) { + Validate.nonNull(shape, "shape"); + Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; + + if (shape instanceof BoxCollisionShape) { + BoxCollisionShape box = (BoxCollisionShape) shape; + box.getHalfExtents(result); + + } else if (shape instanceof CapsuleCollisionShape) { + CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; + float height = capsule.getHeight(); + float radius = capsule.getRadius(); + float axisHalfExtent = height / 2f + radius; + int axisIndex = capsule.getAxis(); + switch (axisIndex) { + case PhysicsSpace.AXIS_X: + result.set(axisHalfExtent, radius, radius); + break; + case PhysicsSpace.AXIS_Y: + result.set(radius, axisHalfExtent, radius); + break; + case PhysicsSpace.AXIS_Z: + result.set(radius, radius, axisHalfExtent); + break; + default: + String message = "axisIndex = " + axisIndex; + throw new IllegalArgumentException(message); + } + + } else if (shape instanceof ConeCollisionShape) { + ConeCollisionShape cone = (ConeCollisionShape) shape; + float height = cone.getHeight(); + float radius = cone.getRadius(); + float axisHalfExtent = height / 2f; + int axisIndex = cone.getAxis(); + switch (axisIndex) { + case PhysicsSpace.AXIS_X: + result.set(axisHalfExtent, radius, radius); + break; + case PhysicsSpace.AXIS_Y: + result.set(radius, axisHalfExtent, radius); + break; + case PhysicsSpace.AXIS_Z: + result.set(radius, radius, axisHalfExtent); + break; + default: + String message = "axisIndex = " + axisIndex; + throw new IllegalArgumentException(message); + } + + } else if (shape instanceof CylinderCollisionShape) { + CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; + cylinder.getHalfExtents(result); + + } else if (shape instanceof HullCollisionShape) { + HullCollisionShape hull = (HullCollisionShape) shape; + hull.getHalfExtents(result); + + } else if (shape instanceof MultiSphere) { + MultiSphere multiSphere = (MultiSphere) shape; + if (multiSphere.countSpheres() == 1) { + float radius = multiSphere.getRadius(0); + result.set(radius, radius, radius); + } + + } else if (shape instanceof SimplexCollisionShape) { + SimplexCollisionShape simplex = (SimplexCollisionShape) shape; + simplex.getHalfExtents(result); + + } else if (shape instanceof SphereCollisionShape) { + SphereCollisionShape sphere = (SphereCollisionShape) shape; + float radius = sphere.getRadius(); + result.set(radius, radius, radius); + + } else { // TODO handle more shapes + String typeName = shape.getClass().getCanonicalName(); + String message = typeName + " lacks half extents."; + throw new IllegalArgumentException(message); + } + + assert MyVector3f.isAllNonNegative(result) : result; + + return result; + } + + /** + * Determine the unscaled height of the specified shape. + * + * @param shape (not null, unaffected) + * @return the unscaled height (≥0) used to create the shape, or NaN if + * that height is undefined or unknown + */ + public static float height(CollisionShape shape) { + Validate.nonNull(shape, "shape"); + + float result = Float.NaN; + if (shape instanceof CapsuleCollisionShape) { + CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; + result = capsule.getHeight(); + + } else if (shape instanceof ConeCollisionShape) { + ConeCollisionShape cone = (ConeCollisionShape) shape; + result = cone.getHeight(); + + } else if (shape instanceof CylinderCollisionShape) { + CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; + Vector3f halfExtents = cylinder.getHalfExtents(null); + int axisIndex = cylinder.getAxis(); + result = 2f * halfExtents.get(axisIndex); + + } else if (shape instanceof MultiSphere) { + MultiSphere multiSphere = (MultiSphere) shape; + if (multiSphere.countSpheres() == 1) { + result = 2f * multiSphere.getRadius(0); + } + + } else if (shape instanceof SphereCollisionShape) { + SphereCollisionShape sphere = (SphereCollisionShape) shape; + result = 2f * sphere.getRadius(); + } + + assert Float.isNaN(result) || result >= 0f : result; + return result; + } + + /** + * Estimate the volume of each child in a CompoundCollisionShape. + * + * @param shape (not null, unaffected) + * @return a new array of volumes (each ≥0) + */ + public static float[] listVolumes(CompoundCollisionShape shape) { + ChildCollisionShape[] children = shape.listChildren(); + int numChildren = children.length; + float[] result = new float[numChildren]; + for (int i = 0; i < numChildren; ++i) { + CollisionShape baseShape = children[i].getShape(); + result[i] = volume(baseShape); + } + + return result; + } + + /** + * Determine the main axis of the specified shape, provided it's a capsule, + * cone, or cylinder. + * + * @param shape (may be null, unaffected) + * @return 0→X, 1→Y, 2→Z, -1→doesn't have a main axis + */ + public static int mainAxisIndex(CollisionShape shape) { + int result = -1; + if (shape instanceof CapsuleCollisionShape) { + CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; + result = capsule.getAxis(); + + } else if (shape instanceof ConeCollisionShape) { + ConeCollisionShape cone = (ConeCollisionShape) shape; + result = cone.getAxis(); + + } else if (shape instanceof CylinderCollisionShape) { + CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; + result = cylinder.getAxis(); + } + + return result; + } + + /** + * Parse the native ID of a shape from its String representation. + * + * @param string the input text (not null, exactly one "#") + * @return the shape's ID + * + * @see com.jme3.bullet.collision.shapes.CollisionShape#toString() + */ + public static long parseNativeId(String string) { + Validate.nonEmpty(string, "string"); + + String[] parts = string.split("#"); + if (parts.length != 2) { + String message = "string = " + MyString.quote(string); + throw new IllegalArgumentException(message); + } + String hexadecimal = parts[1]; + long result = Long.parseLong(hexadecimal, 16); + + return result; + } + + /** + * Determine the unscaled radius of the specified shape. + * + * @param shape (not null, unaffected) + * @return the unscaled radius (≥0) used to create the shape, or NaN if + * that radius is undefined or unknown + */ + public static float radius(CollisionShape shape) { + Validate.nonNull(shape, "shape"); + + float result = Float.NaN; + if (shape instanceof CapsuleCollisionShape) { + CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; + result = capsule.getRadius(); + + } else if (shape instanceof ConeCollisionShape) { + ConeCollisionShape cone = (ConeCollisionShape) shape; + result = cone.getRadius(); + + } else if (shape instanceof CylinderCollisionShape) { + Vector3f halfExtents = halfExtents(shape, null); + int axisIndex = mainAxisIndex(shape); + float r1; + float r2; + switch (axisIndex) { + case PhysicsSpace.AXIS_X: + r1 = halfExtents.y; + r2 = halfExtents.z; + break; + case PhysicsSpace.AXIS_Y: + r1 = halfExtents.x; + r2 = halfExtents.z; + break; + case PhysicsSpace.AXIS_Z: + r1 = halfExtents.x; + r2 = halfExtents.y; + break; + default: + String message = "axisIndex = " + axisIndex; + throw new IllegalArgumentException(message); + } + if (r1 == r2) { + result = r1; + } + + } else if (shape instanceof MultiSphere) { + MultiSphere multiSphere = (MultiSphere) shape; + if (multiSphere.countSpheres() == 1) { + result = multiSphere.getRadius(0); + } + + } else if (shape instanceof SphereCollisionShape) { + result = ((SphereCollisionShape) shape).getRadius(); + } + + assert Float.isNaN(result) || result >= 0f : result; + return result; + } + + /** + * Copy a shape, altering only its half extents. + * + * @param oldShape input shape (not null, unaffected) + * @param newHalfExtents (not null, no negative component, unaffected) + * @return a new shape, or null if not possible + */ + public static CollisionShape setHalfExtents( + CollisionShape oldShape, Vector3f newHalfExtents) { + Validate.nonNull(oldShape, "old shape"); + Validate.nonNegative(newHalfExtents, "new half extents"); + + CollisionShape result; + if (oldShape instanceof BoxCollisionShape) { + result = new BoxCollisionShape(newHalfExtents); + + } else if (oldShape instanceof CapsuleCollisionShape + || oldShape instanceof ConeCollisionShape) { + int axisIndex = mainAxisIndex(oldShape); + float axisHalfExtent; + float radius1; + float radius2; + switch (axisIndex) { + case PhysicsSpace.AXIS_X: + axisHalfExtent = newHalfExtents.x; + radius1 = newHalfExtents.y; + radius2 = newHalfExtents.z; + break; + case PhysicsSpace.AXIS_Y: + axisHalfExtent = newHalfExtents.y; + radius1 = newHalfExtents.x; + radius2 = newHalfExtents.z; + break; + case PhysicsSpace.AXIS_Z: + axisHalfExtent = newHalfExtents.z; + radius1 = newHalfExtents.x; + radius2 = newHalfExtents.y; + break; + default: + String message = "axisIndex = " + axisIndex; + throw new IllegalArgumentException(message); + } + if (radius1 != radius2) { + result = null; + } else if (oldShape instanceof CapsuleCollisionShape) { + float height = 2f * (axisHalfExtent - radius1); + result = new CapsuleCollisionShape(radius1, height, axisIndex); + } else { + assert oldShape instanceof ConeCollisionShape; + float height = 2f * axisHalfExtent; + result = new ConeCollisionShape(radius1, height, axisIndex); + } + + } else if (oldShape instanceof CylinderCollisionShape) { + int axisIndex = mainAxisIndex(oldShape); + result = new CylinderCollisionShape(newHalfExtents, axisIndex); + + } else if (oldShape instanceof SphereCollisionShape) { + if (!MyVector3f.isScaleUniform(newHalfExtents)) { + result = null; + } else { + result = new SphereCollisionShape(newHalfExtents.x); + } + + } else { + result = null; + } + + if (result != null) { + boolean enable = oldShape.isContactFilterEnabled(); + result.setContactFilterEnabled(enable); + + if (!(result instanceof CapsuleCollisionShape) + && !(result instanceof SphereCollisionShape)) { + float margin = oldShape.getMargin(); + result.setMargin(margin); + } + } + + return result; + } + + /** + * Copy a shape, altering only its height. + * + * @param oldShape input shape (not null, unaffected) + * @param newHeight the desired unscaled height (≥0) + * @return a new shape + */ + public static CollisionShape setHeight( + CollisionShape oldShape, float newHeight) { + Validate.nonNull(oldShape, "old shape"); + Validate.nonNegative(newHeight, "new height"); + + CollisionShape result; + if (oldShape instanceof BoxCollisionShape) { + result = setRadius(oldShape, newHeight / 2f); + + } else if (oldShape instanceof CapsuleCollisionShape) { + float radius = radius(oldShape); + int axisIndex = mainAxisIndex(oldShape); + result = new CapsuleCollisionShape(radius, newHeight, axisIndex); + + } else if (oldShape instanceof ConeCollisionShape) { + float radius = radius(oldShape); + int axisIndex = mainAxisIndex(oldShape); + result = new ConeCollisionShape(radius, newHeight, axisIndex); + + } else if (oldShape instanceof CylinderCollisionShape) { + float radius = radius(oldShape); + int axisIndex = mainAxisIndex(oldShape); + result = new CylinderCollisionShape(radius, newHeight, axisIndex); + + } else if (oldShape instanceof SphereCollisionShape) { + result = setRadius(oldShape, newHeight / 2f); + + } else { + result = null; + } + + if (result != null) { + boolean enable = oldShape.isContactFilterEnabled(); + result.setContactFilterEnabled(enable); + + if (!(result instanceof CapsuleCollisionShape) + && !(result instanceof SphereCollisionShape)) { + float margin = oldShape.getMargin(); + result.setMargin(margin); + } + } + + return result; + } + + /** + * Copy a shape, altering only its radius. + * + * @param oldShape input shape (not null, unaffected) + * @param newRadius the desired unscaled radius (≥0) + * @return a new shape + */ + public static CollisionShape setRadius( + CollisionShape oldShape, float newRadius) { + Validate.nonNull(oldShape, "old shape"); + Validate.nonNegative(newRadius, "new radius"); + + CollisionShape result; + if (oldShape instanceof BoxCollisionShape) { + result = new BoxCollisionShape(newRadius); + + } else if (oldShape instanceof CapsuleCollisionShape) { + int axisIndex = mainAxisIndex(oldShape); + float height = height(oldShape); + result = new CapsuleCollisionShape(newRadius, height, axisIndex); + + } else if (oldShape instanceof ConeCollisionShape) { + int axisIndex = mainAxisIndex(oldShape); + float height = height(oldShape); + result = new ConeCollisionShape(newRadius, height, axisIndex); + + } else if (oldShape instanceof CylinderCollisionShape) { + int axisIndex = mainAxisIndex(oldShape); + float height = height(oldShape); + result = new CylinderCollisionShape(newRadius, height, axisIndex); + + } else if (oldShape instanceof SphereCollisionShape) { + result = new SphereCollisionShape(newRadius); + + } else { + result = null; + } + + if (result != null) { + boolean enable = oldShape.isContactFilterEnabled(); + result.setContactFilterEnabled(enable); + + if (!(result instanceof CapsuleCollisionShape) + && !(result instanceof SphereCollisionShape)) { + float margin = oldShape.getMargin(); + result.setMargin(margin); + } + } + + return result; + } + + /** + * Estimate the scaled volume of a closed shape. + * + * @param shape (not null, unaffected) + * @return the volume (in physics-space units cubed, ≥0) + */ + public static float volume(CollisionShape shape) { + float result = shape.scaledVolume(); + assert result >= 0f : result; + return result; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/NegativeAppDataFilter.java b/MinieLibrary/src/main/java/jme3utilities/minie/NegativeAppDataFilter.java index 73dd0d692..a3d06eac9 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/NegativeAppDataFilter.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/NegativeAppDataFilter.java @@ -1,106 +1,106 @@ -/* - Copyright (c) 2019-2021, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsBody; -import java.util.logging.Logger; - -/** - * A simple DebugAppStateFilter that selects any physics objects NOT associated - * with a specific application-data object. Instances are immutable. - * - * @author Stephen Gold sgold@sonic.net - */ -public class NegativeAppDataFilter - implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(NegativeAppDataFilter.class.getName()); - // ************************************************************************* - // fields - - /** - * application-data object (may be null) - */ - final private Object appData; - // ************************************************************************* - // constructors - - /** - * Instantiate a filter for the specified application-data object. - * - * @param appData the desired object (alias created) or null to display/dump - * objects without application data - */ - public NegativeAppDataFilter(Object appData) { - this.appData = appData; - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object should be displayed/dumped. - * - * @param physicsObject the joint or collision object to test (unaffected) - * @return return true if physicsObject should be displayed/dumped, false if - * it shouldn't be - */ - @Override - public boolean displayObject(Object physicsObject) { - boolean result = true; - - if (physicsObject instanceof PhysicsCollisionObject) { - PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; - if (pco.getApplicationData() == appData) { - result = false; - } - - } else if (physicsObject instanceof PhysicsJoint) { - PhysicsJoint joint = (PhysicsJoint) physicsObject; - PhysicsBody a = joint.getBody(JointEnd.A); - if (a != null && a.getApplicationData() == appData) { - result = false; - } else { - PhysicsBody b = joint.getBody(JointEnd.B); - if (b != null && b.getApplicationData() == appData) { - result = false; - } - } - } - - return result; - } -} +/* + Copyright (c) 2019-2021, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsBody; +import java.util.logging.Logger; + +/** + * A simple DebugAppStateFilter that selects any physics objects NOT associated + * with a specific application-data object. Instances are immutable. + * + * @author Stephen Gold sgold@sonic.net + */ +public class NegativeAppDataFilter + implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(NegativeAppDataFilter.class.getName()); + // ************************************************************************* + // fields + + /** + * application-data object (may be null) + */ + final private Object appData; + // ************************************************************************* + // constructors + + /** + * Instantiate a filter for the specified application-data object. + * + * @param appData the desired object (alias created) or null to display/dump + * objects without application data + */ + public NegativeAppDataFilter(Object appData) { + this.appData = appData; + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object should be displayed/dumped. + * + * @param physicsObject the joint or collision object to test (unaffected) + * @return return true if physicsObject should be displayed/dumped, false if + * it shouldn't be + */ + @Override + public boolean displayObject(Object physicsObject) { + boolean result = true; + + if (physicsObject instanceof PhysicsCollisionObject) { + PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; + if (pco.getApplicationData() == appData) { + result = false; + } + + } else if (physicsObject instanceof PhysicsJoint) { + PhysicsJoint joint = (PhysicsJoint) physicsObject; + PhysicsBody a = joint.getBody(JointEnd.A); + if (a != null && a.getApplicationData() == appData) { + result = false; + } else { + PhysicsBody b = joint.getBody(JointEnd.B); + if (b != null && b.getApplicationData() == appData) { + result = false; + } + } + } + + return result; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDescriber.java b/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDescriber.java index 293d79bb0..774be361a 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDescriber.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDescriber.java @@ -1,1288 +1,1288 @@ -/* - Copyright (c) 2013-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.MultiBody; -import com.jme3.bullet.SoftBodyWorldInfo; -import com.jme3.bullet.animation.PhysicsLink; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.shapes.Box2dShape; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.Convex2dShape; -import com.jme3.bullet.collision.shapes.ConvexShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.GImpactCollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.collision.shapes.MinkowskiSum; -import com.jme3.bullet.collision.shapes.MultiSphere; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SimplexCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.joints.Anchor; -import com.jme3.bullet.joints.Constraint; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.joints.SoftAngularJoint; -import com.jme3.bullet.joints.SoftLinearJoint; -import com.jme3.bullet.joints.SoftPhysicsJoint; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationalLimitMotor; -import com.jme3.bullet.joints.motors.TranslationalLimitMotor; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.VehicleWheel; -import com.jme3.bullet.objects.infos.Aero; -import com.jme3.bullet.objects.infos.ConfigFlag; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.material.Material; -import com.jme3.math.Matrix3f; -import com.jme3.math.Plane; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.Control; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.debug.Describer; -import jme3utilities.math.MyVector3f; - -/** - * Generate compact textual descriptions of Minie data structures for debugging - * purposes. - * - * @author Stephen Gold sgold@sonic.net - */ -public class PhysicsDescriber extends Describer { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(PhysicsDescriber.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate a describer with the default separator. - */ - public PhysicsDescriber() { // to avoid a warning from JDK 18 javadoc - } - // ************************************************************************* - // new methods exposed - - /** - * Generate a textual description for a CollisionShape. - * - * @param shape (not null, unaffected) - * @return description (not null) - */ - public String describe(CollisionShape shape) { - Validate.nonNull(shape, "shape"); - - StringBuilder result = new StringBuilder(80); - - String desc = MyShape.describeType(shape); - result.append(desc); - - if (shape instanceof Box2dShape) { - Vector3f he = ((Box2dShape) shape).getHalfExtents(null); - desc = describeHalfExtents(he); - result.append(desc); - - } else if (shape instanceof BoxCollisionShape) { - Vector3f he = ((BoxCollisionShape) shape).getHalfExtents(null); - desc = describeHalfExtents(he); - result.append(desc); - - } else if (shape instanceof CapsuleCollisionShape) { - CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; - int axis = capsule.getAxis(); - desc = MyString.axisName(axis); - result.append(desc); - - float height = capsule.getHeight(); - float radius = capsule.getRadius(); - desc = describeHeightAndRadius(height, radius); - result.append(desc); - - } else if (shape instanceof CompoundCollisionShape) { - CompoundCollisionShape compound = (CompoundCollisionShape) shape; - int numChildren = compound.countChildren(); - desc = String.format("[%d]", numChildren); - result.append(desc); - - } else if (shape instanceof ConeCollisionShape) { - ConeCollisionShape cone = (ConeCollisionShape) shape; - int axis = cone.getAxis(); - desc = MyString.axisName(axis); - result.append(desc); - - float height = cone.getHeight(); - float radius = cone.getRadius(); - desc = describeHeightAndRadius(height, radius); - result.append(desc); - - } else if (shape instanceof Convex2dShape) { - CollisionShape child = ((Convex2dShape) shape).getBaseShape(); - desc = describe(child); - result.append('['); - result.append(desc); - result.append(']'); - - } else if (shape instanceof CylinderCollisionShape) { - CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; - int axis = cylinder.getAxis(); - desc = MyString.axisName(axis); - result.append(desc); - - Vector3f he = cylinder.getHalfExtents(null); - desc = describeHalfExtents(he); - result.append(desc); - - } else if (shape instanceof GImpactCollisionShape) { - int numV = ((GImpactCollisionShape) shape).countMeshVertices(); - desc = String.format("[%d]", numV); - result.append(desc); - - } else if (shape instanceof HeightfieldCollisionShape) { - int numV = ((HeightfieldCollisionShape) shape).countMeshVertices(); - desc = String.format("[%d]", numV); - result.append(desc); - - } else if (shape instanceof HullCollisionShape) { - int numV = ((HullCollisionShape) shape).countHullVertices(); - desc = String.format("[%d]", numV); - result.append(desc); - - } else if (shape instanceof MeshCollisionShape) { - int numV = ((MeshCollisionShape) shape).countMeshVertices(); - desc = String.format("[%d]", numV); - result.append(desc); - - } else if (shape instanceof MinkowskiSum) { - result.append("["); - ConvexShape a = ((MinkowskiSum) shape).getShapeA(); - desc = describe(a); - result.append(desc); - result.append("]+["); - ConvexShape b = ((MinkowskiSum) shape).getShapeB(); - desc = describe(b); - result.append(desc); - result.append(']'); - - } else if (shape instanceof MultiSphere) { - MultiSphere multiSphere = (MultiSphere) shape; - result.append(" r["); - int numSpheres = multiSphere.countSpheres(); - String ls = listSeparator(); - for (int sphereIndex = 0; sphereIndex < numSpheres; ++sphereIndex) { - if (sphereIndex > 0) { - result.append(ls); - } - float radius = multiSphere.getRadius(sphereIndex); - result.append(MyString.describe(radius)); - } - result.append(']'); - - } else if (shape instanceof PlaneCollisionShape) { - Plane plane = ((PlaneCollisionShape) shape).getPlane(); - result.append(" normal["); - Vector3f normal = plane.getNormal(); - result.append(MyVector3f.describe(normal)); - result.append("] constant="); - float constant = plane.getConstant(); - result.append(MyString.describe(constant)); - - } else if (shape instanceof SimplexCollisionShape) { - int numV = ((SimplexCollisionShape) shape).countMeshVertices(); - desc = String.format("[%d]", numV); - result.append(desc); - - } else if (shape instanceof SphereCollisionShape) { - SphereCollisionShape sphere = (SphereCollisionShape) shape; - result.append(" r="); - float radius = sphere.getRadius(); - result.append(MyString.describe(radius)); - - } else { - result.append('?'); - } - - if (shape instanceof HeightfieldCollisionShape - || shape instanceof MeshCollisionShape) { - result.append(' '); - if (!shape.isContactFilterEnabled()) { - result.append("UN"); - } - result.append("filtered"); - } - - result.append(" marg="); - float margin = shape.getMargin(); - result.append(MyString.describe(margin)); - - return result.toString(); - } - - /** - * Generate a brief textual description for a PhysicsJoint. - * - * @param joint (not null, unaffected) - * @return description (not null, not empty) - */ - public String describe(PhysicsJoint joint) { - StringBuilder result = new StringBuilder(40); - - String type = joint.getClass().getSimpleName(); - if (type.endsWith("Joint")) { - type = MyString.removeSuffix(type, "Joint"); - } - result.append(type); - - if (!joint.isEnabled()) { - result.append(" DISABLED"); - } - - return result.toString(); - } - - /** - * Describe the specified RotationalLimitMotor. - * - * @param motor the motor to describe (not null, unaffected) - * @return descriptive text (not null, not empty) - */ - public String describe(RotationalLimitMotor motor) { - StringBuilder result = new StringBuilder(80); - - if (motor.isEnableMotor()) { - float angle = motor.getAngle(); - result.append(angle); - - float lo = motor.getLowerLimit(); - float hi = motor.getUpperLimit(); - if (hi < lo) { - result.append(" unlimited"); - } else { - result.append(" lo="); - result.append(MyString.describe(lo)); - result.append(" hi="); - result.append(MyString.describe(hi)); - } - - result.append(" tgtV="); - float targetV = motor.getTargetVelocity(); - result.append(MyString.describe(targetV)); - - result.append(" cfm="); - float cfm = motor.getNormalCFM(); - result.append(MyString.describe(cfm)); - - result.append(" damp="); - float damping = motor.getDamping(); - result.append(MyString.describe(damping)); - - result.append(" maxMF="); - float maxMF = motor.getMaxMotorForce(); - result.append(MyString.describe(maxMF)); - - if (hi >= lo) { - result.append(" lim[cfm="); - cfm = motor.getStopCFM(); - result.append(MyString.describe(cfm)); - - result.append(" erp="); - float erp = motor.getERP(); - result.append(MyString.describe(erp)); - - result.append(" maxMF="); - maxMF = motor.getMaxLimitForce(); - result.append(MyString.describe(maxMF)); - - result.append(" rest="); - float rest = motor.getRestitution(); - result.append(MyString.describe(rest)); - - result.append(" soft="); - float soft = motor.getLimitSoftness(); - result.append(MyString.describe(soft)); - result.append(']'); - } - } else { - result.append(" DISABLED"); - } - - return result.toString(); - } - - /** - * Generate a brief textual description for the specified - * PhysicsSoftBody.Material - * - * @param material the Material to describe (not null, unaffected) - * @return description (not null, not empty) - */ - public String describe(SoftBodyMaterial material) { - String result = String.format( - "Material stiffness[ang=%s lin=%s vol=%s]", - MyString.describe(material.angularStiffness()), - MyString.describe(material.linearStiffness()), - MyString.describe(material.volumeStiffness())); - return result; - } - - /** - * Generate a brief textual description for the specified SoftBodyWorldInfo. - * - * @param info the info to describe (not null, unaffected) - * @return description (not null, not empty) - */ - public String describe(SoftBodyWorldInfo info) { - StringBuilder result = new StringBuilder(40); - - result.append("SbwInfo grav["); - Vector3f grav = info.copyGravity(null); - String description = MyVector3f.describe(grav); - result.append(description); - - result.append("] offset="); - float offset = info.waterOffset(); - description = MyString.describe(offset); - result.append(description); - - result.append(" norm["); - Vector3f norm = info.copyWaterNormal(null); - description = MyVector3f.describe(norm); - result.append(description); - - result.append("] water="); - float water = info.waterDensity(); - description = MyString.describe(water); - result.append(description); - - result.append(" air="); - float air = info.airDensity(); - description = MyString.describe(air); - result.append(description); - - result.append(" maxDisp="); - float maxDisp = info.maxDisplacement(); - description = MyString.describe(maxDisp); - result.append(description); - - return result.toString(); - } - - /** - * Describe the indexed axis of the specified TranslationalLimitMotor. - * - * @param motor the motor to describe (not null, unaffected) - * @param axisIndex which axis: 0→X, 1→Y, 2→Z - * @return descriptive text (not null, not empty) - */ - public String describe(TranslationalLimitMotor motor, int axisIndex) { - Validate.axisIndex(axisIndex, "axis index"); - StringBuilder result = new StringBuilder(80); - Vector3f tmpVector = new Vector3f(); - - if (motor.isEnabled(axisIndex)) { - float offset = motor.getOffset(tmpVector).get(axisIndex); - result.append(offset); - - float lo = motor.getLowerLimit(tmpVector).get(axisIndex); - float hi = motor.getUpperLimit(tmpVector).get(axisIndex); - if (hi < lo) { - result.append(" unlimited"); - } else { - result.append(" lo="); - result.append(MyString.describe(lo)); - result.append(" hi="); - result.append(MyString.describe(hi)); - } - - result.append(" tgtV="); - float targetV = motor.getTargetVelocity(tmpVector).get(axisIndex); - result.append(MyString.describe(targetV)); - - result.append(" cfm="); - float cfm = motor.getNormalCFM(tmpVector).get(axisIndex); - result.append(MyString.describe(cfm)); - - result.append(" damp="); - float damping = motor.getDamping(); - result.append(MyString.describe(damping)); - - result.append(" maxMF="); - float maxMF = motor.getMaxMotorForce(tmpVector).get(axisIndex); - result.append(MyString.describe(maxMF)); - - if (hi >= lo) { - result.append(" lim[cfm="); - cfm = motor.getStopCFM(tmpVector).get(axisIndex); - result.append(MyString.describe(cfm)); - - result.append(" erp="); - float erp = motor.getERP(tmpVector).get(axisIndex); - result.append(MyString.describe(erp)); - - result.append(" rest="); - float rest = motor.getRestitution(); - result.append(MyString.describe(rest)); - - result.append(" soft="); - float softness = motor.getLimitSoftness(); - result.append(MyString.describe(softness)); - result.append(']'); - } - } else { - result.append(" DISABLED"); - } - - return result.toString(); - } - - /** - * Describe the specified VehicleWheel. - * - * @param wheel the wheel to describe (not null, unaffected) - * @return descriptive text (not null, not empty) - */ - public String describe(VehicleWheel wheel) { - StringBuilder result = new StringBuilder(80); - - boolean isFront = wheel.isFrontWheel(); - if (isFront) { - result.append("frnt"); - } else { - result.append("rear"); - } - - result.append(" r="); - float r = wheel.getRadius(); - result.append(MyString.describe(r)); - - result.append(" loc["); - Vector3f loc = wheel.getLocation(null); - result.append(MyVector3f.describe(loc)); - - result.append("] axleDir["); - Vector3f axleDir = wheel.getAxle(null); - result.append(MyVector3f.describe(axleDir)); - - result.append("] fSlip="); - float fSlip = wheel.getFrictionSlip(); - result.append(MyString.describe(fSlip)); - - result.append(" rollInf="); - float rollInf = wheel.getRollInfluence(); - result.append(MyString.describe(rollInf)); - - result.append(" sus[damp[co="); - float co = wheel.getWheelsDampingCompression(); - result.append(MyString.describe(co)); - - result.append(" re="); - float re = wheel.getWheelsDampingRelaxation(); - result.append(MyString.describe(re)); - - result.append("] down["); - Vector3f down = wheel.getDirection(null); - result.append(MyVector3f.describe(down)); - - result.append("] maxF="); - float maxF = wheel.getMaxSuspensionForce(); - result.append(MyString.describe(maxF)); - - result.append("] maxTrav="); - float maxTrav = wheel.getMaxSuspensionTravelCm(); - result.append(MyString.describe(maxTrav)); - - result.append(" restL="); - float restL = wheel.getRestLength(); - result.append(MyString.describe(restL)); - - result.append(" stiff="); - float stiff = wheel.getSuspensionStiffness(); - result.append(MyString.describe(stiff)); - - result.append(']'); - - return result.toString(); - } - - /** - * Generate the first line of a textual description for the specified - * SoftBodyConfig. - * - * @param config the config to describe (not null, unaffected) - * @return description (not null, not empty) - */ - public String describe1(SoftBodyConfig config) { - StringBuilder result = new StringBuilder(120); - - result.append("Config aero="); - Aero aeroModel = config.aerodynamics(); - String description = aeroModel.toString(); - result.append(description); - - result.append(" flags="); - int collisionFlags = config.collisionFlags(); - description = ConfigFlag.describe(collisionFlags); - result.append(description); - - description = String.format(" maxVolRatio=%s timeScale=%s velCorr=%s", - MyString.describe(config.get(Sbcp.MaxVolumeRatio)), - MyString.describe(config.get(Sbcp.TimeScale)), - MyString.describe(config.get(Sbcp.VelocityCorrection))); - result.append(description); - - description = String.format(" coef[damp=%s drag=%s fric=%s lift=%s" - + " pose=%s pres=%s volCons=%s]", - MyString.describe(config.get(Sbcp.Damping)), - MyString.describe(config.get(Sbcp.Drag)), - MyString.describe(config.get(Sbcp.DynamicFriction)), - MyString.describe(config.get(Sbcp.Lift)), - MyString.describe(config.get(Sbcp.PoseMatching)), - MyString.describe(config.get(Sbcp.Pressure)), - MyString.describe(config.get(Sbcp.VolumeConservation))); - result.append(description); - - return result.toString(); - } - - /** - * Generate the 2nd line of a brief textual description for the specified - * SoftBodyConfig. - * - * @param config the config to describe (not null, unaffected) - * @return description (not null, not empty) - */ - public String describe2(SoftBodyConfig config) { - StringBuilder result = new StringBuilder(120); - - String description = String.format( - " hardness[a=%s clk=%s clr=%s cls=%s k=%s r=%s s=%s]", - MyString.describe(config.get(Sbcp.AnchorHardness)), - MyString.describe(config.get(Sbcp.ClusterKineticHardness)), - MyString.describe(config.get(Sbcp.ClusterRigidHardness)), - MyString.describe(config.get(Sbcp.ClusterSoftHardness)), - MyString.describe(config.get(Sbcp.KineticHardness)), - MyString.describe(config.get(Sbcp.RigidHardness)), - MyString.describe(config.get(Sbcp.SoftHardness))); - result.append(description); - - description = String.format(" impSplit[clk=%s clr=%s cls=%s]", - MyString.describe(config.get(Sbcp.ClusterKineticSplit)), - MyString.describe(config.get(Sbcp.ClusterRigidSplit)), - MyString.describe(config.get(Sbcp.ClusterSoftSplit))); - result.append(description); - - description = String.format(" iters[cl=%d drift=%d pos=%d vel=%d]", - config.clusterIterations(), - config.driftIterations(), - config.positionIterations(), - config.velocityIterations()); - result.append(description); - - return result.toString(); - } - - /** - * Generate the 2nd line of a textual description for the specified - * VehicleWheel. - * - * @param wheel the wheel to describe (not null, unaffected) - * @return description (not null, not empty) - */ - public String describe2(VehicleWheel wheel) { - StringBuilder result = new StringBuilder(120); - - result.append(" brake="); - float brake = wheel.getBrake(); - result.append(MyString.describe(brake)); - - result.append(" engF="); - float engF = wheel.getEngineForce(); - result.append(MyString.describe(engF)); - - result.append(" steer="); - float steer = wheel.getSteerAngle(); - result.append(MyString.describe(steer)); - - result.append(" susLen="); - float susLen = wheel.getSuspensionLength(); - result.append(MyString.describe(susLen)); - - return result.toString(); - } - - /** - * Describe the angular components of the specified SixDofJoint. - * - * @param joint the joint to describe (not null, unaffected) - * @return descriptive text (not null, not empty) - */ - public String describeAngular(SixDofJoint joint) { - StringBuilder result = new StringBuilder(80); - - result.append("angles["); - Vector3f angles = joint.getAngles(new Vector3f()); - result.append(MyVector3f.describe(angles)); - - result.append("] lo["); // TODO describe axis-by-axis - Vector3f lower = joint.getAngularLowerLimit(new Vector3f()); - result.append(MyVector3f.describe(lower)); - - result.append("] hi["); - Vector3f upper = joint.getAngularUpperLimit(new Vector3f()); - result.append(MyVector3f.describe(upper)); - result.append(']'); - - return result.toString(); - } - - /** - * Describe the application data of a collision object. - * - * @param pco the collision object to describe (not null, unaffected) - * @return a descriptive string (not null, may be empty) - */ - public String describeApplicationData(PhysicsCollisionObject pco) { - Validate.nonNull(pco, "collision object"); - - String result = ""; - Object aData = pco.getApplicationData(); - if (aData != null) { - StringBuilder builder = new StringBuilder(64); - builder.append(" aData="); - appendObjectDescription(builder, aData); - result = builder.toString(); - } - - return result; - } - - /** - * Describe the indexed degree of freedom of the specified New6Dof - * constraint. - * - * @param constraint the constraint to describe (not null, unaffected) - * @param dofIndex which degree of freedom (0→X translation, 1→Y - * translation, 2→Z translation, 3→X rotation, 4→Y rotation, - * 5→Z rotation) - * @return descriptive text (not null, not empty) - */ - public String describeDof(New6Dof constraint, int dofIndex) { - Validate.inRange(dofIndex, "DOF index", 0, 5); - StringBuilder result = new StringBuilder(80); - - float lo = constraint.get(MotorParam.LowerLimit, dofIndex); - float hi = constraint.get(MotorParam.UpperLimit, dofIndex); - if (hi < lo) { - result.append(" free"); - } else if (hi == lo) { - result.append(" lock["); - result.append(MyString.describe(lo)); - result.append(']'); - } else { - result.append(" lims["); - result.append(MyString.describe(lo)); - result.append(' '); - result.append(MyString.describe(hi)); - result.append(']'); - } - - result.append(" motor["); - if (constraint.isMotorEnabled(dofIndex)) { - if (constraint.isServoEnabled(dofIndex)) { // servo motor - result.append("servo target="); - float target = constraint.get(MotorParam.ServoTarget, dofIndex); - result.append(MyString.describe(target)); - result.append(" "); - } - - result.append("tgtV="); - float tgtV = constraint.get(MotorParam.TargetVelocity, dofIndex); - result.append(MyString.describe(tgtV)); - - result.append(" cfm="); - float cfm = constraint.get(MotorParam.MotorCfm, dofIndex); - result.append(MyString.describe(cfm)); - - result.append(" erp="); - float erp = constraint.get(MotorParam.MotorErp, dofIndex); - result.append(MyString.describe(erp)); - - result.append(" maxF="); - float maxF = constraint.get(MotorParam.MaxMotorForce, dofIndex); - result.append(MyString.describe(maxF)); - - } else { - result.append("off"); - } - result.append(']'); - - if (hi >= lo) { // limits/stops are enabled - result.append(" lim[bounce="); - float bounce = constraint.get(MotorParam.Bounce, dofIndex); - result.append(MyString.describe(bounce)); - - result.append(" cfm="); - float cfm = constraint.get(MotorParam.StopCfm, dofIndex); - result.append(MyString.describe(cfm)); - - result.append(" erp="); - float erp = constraint.get(MotorParam.StopErp, dofIndex); - result.append(MyString.describe(erp)); - - result.append(']'); - } - - result.append(" spring["); - if (constraint.isSpringEnabled(dofIndex)) { - result.append("eq="); - float eq = constraint.get(MotorParam.Equilibrium, dofIndex); - result.append(MyString.describe(eq)); - - result.append(" stif="); - float stif = constraint.get(MotorParam.Stiffness, dofIndex); - result.append(MyString.describe(stif)); - - result.append(" damp="); - float damp = constraint.get(MotorParam.Damping, dofIndex); - result.append(MyString.describe(damp)); - } else { - result.append("off"); - } - result.append(']'); - - return result.toString(); - } - - /** - * Briefly describe the collision group and collide-with groups of the - * specified MultiBody. - * - * @param multiBody the object to describe (not null, unaffected) - * @return descriptive text (not null, may be empty) - */ - String describeGroups(MultiBody multiBody) { - StringBuilder result = new StringBuilder(40); - - int group = multiBody.collisionGroup(); - if (group != PhysicsCollisionObject.COLLISION_GROUP_01) { - result.append(" group=0x"); - result.append(Integer.toHexString(group)); - } - - int groupMask = multiBody.collideWithGroups(); - if (groupMask != PhysicsCollisionObject.COLLISION_GROUP_01) { - result.append(" gMask=0x"); - result.append(Integer.toHexString(groupMask)); - } - - return result.toString(); - } - - /** - * Briefly describe the collision group and collide-with groups of the - * specified collision object. - * - * @param pco the object to describe (not null, unaffected) - * @return descriptive text (not null, may be empty) - */ - public String describeGroups(PhysicsCollisionObject pco) { - StringBuilder result = new StringBuilder(40); - - int group = pco.getCollisionGroup(); - if (group != PhysicsCollisionObject.COLLISION_GROUP_01) { - result.append(" group=0x"); - result.append(Integer.toHexString(group)); - } - - int groupMask = pco.getCollideWithGroups(); - if (groupMask != PhysicsCollisionObject.COLLISION_GROUP_01) { - result.append(" gMask=0x"); - result.append(Integer.toHexString(groupMask)); - } - - return result.toString(); - } - - /** - * Describe the specified PhysicsJoint in the context of the specified body. - * - * @param joint the joint to describe (not null, unaffected) - * @param body one end of the joint - * @param forceId true to force inclusion of the PCO native IDs, false to - * include them only when there's no user or application data - * @return descriptive text (not null, not empty) - */ - public String describeJointInBody( - PhysicsJoint joint, PhysicsBody body, boolean forceId) { - StringBuilder result = new StringBuilder(80); - - String desc = describe(joint); - result.append(desc); - - if (joint.countEnds() == 1) { - result.append(" single-ended"); - } else { - result.append(" to"); - PhysicsBody otherBody = joint.findOtherBody(body); - appendPco(result, otherBody, forceId); - } - - JointEnd end = joint.findEnd(body); - if (joint instanceof Constraint) { - Constraint constraint = (Constraint) joint; - result.append(" piv["); - Vector3f piv = constraint.getPivot(end, null); - result.append(MyVector3f.describe(piv)); - result.append(']'); - } - - if (joint instanceof New6Dof) { - New6Dof sixDof = (New6Dof) joint; - result.append(" rot["); - Matrix3f rot = sixDof.getRotationMatrix(end, null); - result.append(MyString.describeMatrix(rot)); - result.append(']'); - } else if (joint instanceof SoftAngularJoint) { - SoftAngularJoint saj = (SoftAngularJoint) joint; - result.append(" axis["); - Vector3f axis = saj.copyAxis(null); - result.append(MyVector3f.describe(axis)); - result.append(']'); - } else if (joint instanceof SoftLinearJoint) { - SoftLinearJoint slj = (SoftLinearJoint) joint; - result.append(" loc["); - Vector3f loc = slj.copyLocation(null); - result.append(MyVector3f.describe(loc)); - result.append(']'); - } - - return result.toString(); - } - - /** - * Describe the specified joint in the context of a PhysicsSpace. - * - * @param joint the joint to describe (not null, unaffected) - * @param forceIds true to force inclusion of PCO native IDs, false to - * include them only when there's no user or application data - * @return descriptive text (not null, not empty) - */ - public String describeJointInSpace(PhysicsJoint joint, boolean forceIds) { - String result; - if (joint instanceof Anchor) { - result = describeAnchorInSpace((Anchor) joint, forceIds); - } else if (joint instanceof Constraint) { - result = describeConstraintInSpace((Constraint) joint, forceIds); - } else { - result = describeSoftJointInSpace( - (SoftPhysicsJoint) joint, forceIds); - } - - return result; - } - - /** - * Describe the linear components of the specified SixDofJoint. - * - * @param joint the joint to describe (not null, unaffected) - * @return descriptive text (not null, not empty) - */ - public String describeLinear(SixDofJoint joint) { - StringBuilder result = new StringBuilder(80); - - result.append("offset["); - Vector3f offset = joint.getPivotOffset(new Vector3f()); - result.append(MyVector3f.describe(offset)); - - result.append("] lo["); // TODO describe axis-by-axis - Vector3f lo = joint.getLinearLowerLimit(new Vector3f()); - result.append(MyVector3f.describe(lo)); - - result.append("] hi["); - Vector3f hi = joint.getLinearUpperLimit(new Vector3f()); - result.append(MyVector3f.describe(hi)); - result.append(']'); - - return result.toString(); - } - - /** - * Describe a collision object in the context of another PCO. - * - * @param pco the object to describe (not null, unaffected) - * @param forceId true to force inclusion of the native ID, false to include - * it only if there's no user or application data - * @return descriptive text (not null, not empty) - */ - String describePco(PhysicsCollisionObject pco, boolean forceId) { - StringBuilder result = new StringBuilder(80); - appendPco(result, pco, forceId); - return result.toString(); - } - - /** - * Describe the user of a collision object. - * - * @param pco the collision object to describe (not null, unaffected) - * @return a descriptive string (not null, may be empty) - */ - public String describeUser(PhysicsCollisionObject pco) { - Validate.nonNull(pco, "collision object"); - - String result = ""; - Object user = pco.getUserObject(); - if (user != null) { - StringBuilder builder = new StringBuilder(64); - builder.append(" user="); - appendObjectDescription(builder, user); - result = builder.toString(); - } - - return result; - } - // ************************************************************************* - // Describer methods - - /** - * Create a copy of this PhysicsDescriber. - * - * @return a new instance, equivalent to this one - * @throws CloneNotSupportedException if the superclass isn't cloneable - */ - @Override - public PhysicsDescriber clone() throws CloneNotSupportedException { - PhysicsDescriber clone = (PhysicsDescriber) super.clone(); - return clone; - } - - /** - * Generate a textual description of a scene-graph control. - * - * @param control (not null) - * @return description (not null) - */ - @Override - protected String describe(Control control) { - Validate.nonNull(control, "control"); - String result = MyControlP.describe(control); - return result; - } - - /** - * Test whether a scene-graph control is enabled. - * - * @param control control to test (not null) - * @return true if the control is enabled, otherwise false - */ - @Override - protected boolean isControlEnabled(Control control) { - Validate.nonNull(control, "control"); - - boolean result = !MyControlP.canDisable(control) - || MyControlP.isEnabled(control); - - return result; - } - // ************************************************************************* - // private methods - - /** - * Append a description of an arbitrary Object, such as a PCO's user. - * - * @param builder the StringBuilder to append to (not null, modified) - * @param subject the Object to describe (not null, unaffected) - */ - private static void appendObjectDescription( - StringBuilder builder, Object subject) { - String className = subject.getClass().getSimpleName(); - - String desc; - if (subject instanceof Material) { - builder.append(className); - desc = ((Material) subject).getName(); - } else if (subject instanceof PhysicsLink) { - builder.append(className); - desc = ((PhysicsLink) subject).boneName(); - } else if (subject instanceof Spatial) { - builder.append(className); - desc = ((Spatial) subject).getName(); - } else if (subject instanceof String) { - builder.append("String"); - desc = (String) subject; - } else { - desc = subject.toString(); - } - - if (desc != null) { - if (desc.length() > 50) { - desc = desc.substring(0, 47) + "..."; - } - builder.append(MyString.quote(desc)); - } - } - - /** - * Append a description of a collision object in the context of another PCO. - * - * @param builder the StringBuilder to append to (not null, modified) - * @param pco the object to describe (not null, unaffected) - * @param forceId true to force inclusion of the native ID, false to include - * it only if there's no user or application data - */ - private void appendPco(StringBuilder builder, PhysicsCollisionObject pco, - boolean forceId) { - String desc; - if (pco.getApplicationData() == null && pco.getUserObject() == null) { - desc = pco.toString(); - builder.append(desc); - - } else { - builder.append('['); - - if (forceId) { - desc = pco.toString(); - } else { - desc = pco.getClass().getSimpleName(); - desc = desc.replace("Body", ""); - desc = desc.replace("Control", "C"); - desc = desc.replace("Physics", ""); - desc = desc.replace("Object", ""); - } - builder.append(desc); - - desc = describeApplicationData(pco); - builder.append(desc); - - desc = describeUser(pco); - builder.append(desc); - - builder.append(']'); - } - - if (!pco.isInWorld()) { - builder.append("_NOT_IN_WORLD"); - } - } - - /** - * Describe the specified Anchor in the context of a PhysicsSpace. - * - * @param anchor the Anchor to describe (not null, unaffected) - * @param forceIds true to force inclusion of the PCO native IDs, false to - * include them only when there's no user or application data - * @return descriptive text (not null, not empty) - */ - private String describeAnchorInSpace(Anchor anchor, boolean forceIds) { - StringBuilder result = new StringBuilder(80); - - String desc = describe(anchor); - result.append(desc); - - result.append(" a="); - PhysicsSoftBody a = anchor.getSoftBody(); - appendPco(result, a, forceIds); - - result.append(" ["); - int nodeIndex = anchor.nodeIndex(); - result.append(nodeIndex); - result.append(']'); - - result.append(" b="); - PhysicsRigidBody b = anchor.getRigidBody(); - appendPco(result, b, forceIds); - - result.append(" piv["); - Vector3f piv = anchor.copyPivot(null); - result.append(MyVector3f.describe(piv)); - result.append(']'); - - result.append(" infl="); - float infl = anchor.influence(); - result.append(MyString.describe(infl)); - - return result.toString(); - } - - /** - * Describe the specified Constraint in the context of a PhysicsSpace. - * - * @param constraint the Constraint to describe (not null, unaffected) - * @param forceIds true to force inclusion of the PCO native IDs, false to - * include them only when there's no user or application data - * @return descriptive text (not null, not empty) - */ - private String describeConstraintInSpace( - Constraint constraint, boolean forceIds) { - StringBuilder result = new StringBuilder(80); - - String desc = describe(constraint); - result.append(desc); - - if (constraint.countEnds() == 2) { - boolean endsCollide = constraint.isCollisionBetweenLinkedBodies(); - if (endsCollide) { - result.append(" collide"); - } else { - result.append(" NOcollide"); - } - } - - int iters = constraint.getOverrideIterations(); - if (iters != -1) { - result.append(" iters="); - result.append(iters); - } - - int numDyn = 0; - PhysicsRigidBody a = constraint.getBodyA(); - if (a != null) { - result.append(" a:"); - appendPco(result, a, forceIds); - if (a.isDynamic()) { - ++numDyn; - } - } - - PhysicsRigidBody b = constraint.getBodyB(); - if (b != null) { - result.append(" b:"); - appendPco(result, b, forceIds); - if (b.isDynamic()) { - ++numDyn; - } - } - - if (numDyn == 0) { - result.append(" NO_DYNAMIC_END"); - } - - if (constraint.isFeedback()) { - float impulse = constraint.getAppliedImpulse(); - result.append(" impulse="); - result.append(impulse); - } - - float bit = constraint.getBreakingImpulseThreshold(); - if (bit != Float.MAX_VALUE) { - result.append(" bit="); - result.append(MyString.describe(bit)); - } - - if (forceIds) { - result.append(" #"); - long id = constraint.nativeId(); - String hex = Long.toHexString(id); - result.append(hex); - } - - return result.toString(); - } - - /** - * Describe the specified height and radius. - * - * @param height the height of the shape - * @param radius the radius of the shape - * @return a bracketed description (not null, not empty) - */ - private static String describeHeightAndRadius(float height, float radius) { - String hText = MyString.describe(height); - String rText = MyString.describe(radius); - String result = String.format(" h=%s r=%s", hText, rText); - - return result; - } - - /** - * Describe the specified soft joint in the context of a PhysicsSpace. - * - * @param joint the soft joint to describe (not null, unaffected) - * @param forceIds true to force inclusion of the PCO native IDs, false to - * include them only when there's no user or application data - * @return descriptive text (not null, not empty) - */ - private String describeSoftJointInSpace( - SoftPhysicsJoint joint, boolean forceIds) { - StringBuilder result = new StringBuilder(80); - - String desc = describe(joint); - result.append(desc); - - PhysicsSoftBody a = joint.getSoftBodyA(); - result.append(" a="); - appendPco(result, a, forceIds); - - result.append(" ["); - int clusterIndex = joint.clusterIndexA(); - result.append(clusterIndex); - result.append(']'); - - PhysicsBody b = joint.getBody(JointEnd.B); - result.append(" b="); - appendPco(result, b, forceIds); - - if (joint.isSoftSoft()) { - result.append(" ["); - clusterIndex = joint.clusterIndexB(); - result.append(clusterIndex); - result.append(']'); - } - - result.append(" cfm="); - float cfm = joint.getCFM(); - result.append(MyString.describe(cfm)); - - result.append(" erp="); - float erp = joint.getERP(); - result.append(MyString.describe(erp)); - - result.append(" split="); - float split = joint.getSplit(); - result.append(MyString.describe(split)); - - return result.toString(); - } -} +/* + Copyright (c) 2013-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.MultiBody; +import com.jme3.bullet.SoftBodyWorldInfo; +import com.jme3.bullet.animation.PhysicsLink; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.Box2dShape; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.ConeCollisionShape; +import com.jme3.bullet.collision.shapes.Convex2dShape; +import com.jme3.bullet.collision.shapes.ConvexShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.GImpactCollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.MinkowskiSum; +import com.jme3.bullet.collision.shapes.MultiSphere; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SimplexCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.joints.Anchor; +import com.jme3.bullet.joints.Constraint; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.bullet.joints.SoftAngularJoint; +import com.jme3.bullet.joints.SoftLinearJoint; +import com.jme3.bullet.joints.SoftPhysicsJoint; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationalLimitMotor; +import com.jme3.bullet.joints.motors.TranslationalLimitMotor; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.VehicleWheel; +import com.jme3.bullet.objects.infos.Aero; +import com.jme3.bullet.objects.infos.ConfigFlag; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.material.Material; +import com.jme3.math.Matrix3f; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.debug.Describer; +import jme3utilities.math.MyVector3f; + +/** + * Generate compact textual descriptions of Minie data structures for debugging + * purposes. + * + * @author Stephen Gold sgold@sonic.net + */ +public class PhysicsDescriber extends Describer { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(PhysicsDescriber.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate a describer with the default separator. + */ + public PhysicsDescriber() { // to avoid a warning from JDK 18 javadoc + } + // ************************************************************************* + // new methods exposed + + /** + * Generate a textual description for a CollisionShape. + * + * @param shape (not null, unaffected) + * @return description (not null) + */ + public String describe(CollisionShape shape) { + Validate.nonNull(shape, "shape"); + + StringBuilder result = new StringBuilder(80); + + String desc = MyShape.describeType(shape); + result.append(desc); + + if (shape instanceof Box2dShape) { + Vector3f he = ((Box2dShape) shape).getHalfExtents(null); + desc = describeHalfExtents(he); + result.append(desc); + + } else if (shape instanceof BoxCollisionShape) { + Vector3f he = ((BoxCollisionShape) shape).getHalfExtents(null); + desc = describeHalfExtents(he); + result.append(desc); + + } else if (shape instanceof CapsuleCollisionShape) { + CapsuleCollisionShape capsule = (CapsuleCollisionShape) shape; + int axis = capsule.getAxis(); + desc = MyString.axisName(axis); + result.append(desc); + + float height = capsule.getHeight(); + float radius = capsule.getRadius(); + desc = describeHeightAndRadius(height, radius); + result.append(desc); + + } else if (shape instanceof CompoundCollisionShape) { + CompoundCollisionShape compound = (CompoundCollisionShape) shape; + int numChildren = compound.countChildren(); + desc = String.format("[%d]", numChildren); + result.append(desc); + + } else if (shape instanceof ConeCollisionShape) { + ConeCollisionShape cone = (ConeCollisionShape) shape; + int axis = cone.getAxis(); + desc = MyString.axisName(axis); + result.append(desc); + + float height = cone.getHeight(); + float radius = cone.getRadius(); + desc = describeHeightAndRadius(height, radius); + result.append(desc); + + } else if (shape instanceof Convex2dShape) { + CollisionShape child = ((Convex2dShape) shape).getBaseShape(); + desc = describe(child); + result.append('['); + result.append(desc); + result.append(']'); + + } else if (shape instanceof CylinderCollisionShape) { + CylinderCollisionShape cylinder = (CylinderCollisionShape) shape; + int axis = cylinder.getAxis(); + desc = MyString.axisName(axis); + result.append(desc); + + Vector3f he = cylinder.getHalfExtents(null); + desc = describeHalfExtents(he); + result.append(desc); + + } else if (shape instanceof GImpactCollisionShape) { + int numV = ((GImpactCollisionShape) shape).countMeshVertices(); + desc = String.format("[%d]", numV); + result.append(desc); + + } else if (shape instanceof HeightfieldCollisionShape) { + int numV = ((HeightfieldCollisionShape) shape).countMeshVertices(); + desc = String.format("[%d]", numV); + result.append(desc); + + } else if (shape instanceof HullCollisionShape) { + int numV = ((HullCollisionShape) shape).countHullVertices(); + desc = String.format("[%d]", numV); + result.append(desc); + + } else if (shape instanceof MeshCollisionShape) { + int numV = ((MeshCollisionShape) shape).countMeshVertices(); + desc = String.format("[%d]", numV); + result.append(desc); + + } else if (shape instanceof MinkowskiSum) { + result.append("["); + ConvexShape a = ((MinkowskiSum) shape).getShapeA(); + desc = describe(a); + result.append(desc); + result.append("]+["); + ConvexShape b = ((MinkowskiSum) shape).getShapeB(); + desc = describe(b); + result.append(desc); + result.append(']'); + + } else if (shape instanceof MultiSphere) { + MultiSphere multiSphere = (MultiSphere) shape; + result.append(" r["); + int numSpheres = multiSphere.countSpheres(); + String ls = listSeparator(); + for (int sphereIndex = 0; sphereIndex < numSpheres; ++sphereIndex) { + if (sphereIndex > 0) { + result.append(ls); + } + float radius = multiSphere.getRadius(sphereIndex); + result.append(MyString.describe(radius)); + } + result.append(']'); + + } else if (shape instanceof PlaneCollisionShape) { + Plane plane = ((PlaneCollisionShape) shape).getPlane(); + result.append(" normal["); + Vector3f normal = plane.getNormal(); + result.append(MyVector3f.describe(normal)); + result.append("] constant="); + float constant = plane.getConstant(); + result.append(MyString.describe(constant)); + + } else if (shape instanceof SimplexCollisionShape) { + int numV = ((SimplexCollisionShape) shape).countMeshVertices(); + desc = String.format("[%d]", numV); + result.append(desc); + + } else if (shape instanceof SphereCollisionShape) { + SphereCollisionShape sphere = (SphereCollisionShape) shape; + result.append(" r="); + float radius = sphere.getRadius(); + result.append(MyString.describe(radius)); + + } else { + result.append('?'); + } + + if (shape instanceof HeightfieldCollisionShape + || shape instanceof MeshCollisionShape) { + result.append(' '); + if (!shape.isContactFilterEnabled()) { + result.append("UN"); + } + result.append("filtered"); + } + + result.append(" marg="); + float margin = shape.getMargin(); + result.append(MyString.describe(margin)); + + return result.toString(); + } + + /** + * Generate a brief textual description for a PhysicsJoint. + * + * @param joint (not null, unaffected) + * @return description (not null, not empty) + */ + public String describe(PhysicsJoint joint) { + StringBuilder result = new StringBuilder(40); + + String type = joint.getClass().getSimpleName(); + if (type.endsWith("Joint")) { + type = MyString.removeSuffix(type, "Joint"); + } + result.append(type); + + if (!joint.isEnabled()) { + result.append(" DISABLED"); + } + + return result.toString(); + } + + /** + * Describe the specified RotationalLimitMotor. + * + * @param motor the motor to describe (not null, unaffected) + * @return descriptive text (not null, not empty) + */ + public String describe(RotationalLimitMotor motor) { + StringBuilder result = new StringBuilder(80); + + if (motor.isEnableMotor()) { + float angle = motor.getAngle(); + result.append(angle); + + float lo = motor.getLowerLimit(); + float hi = motor.getUpperLimit(); + if (hi < lo) { + result.append(" unlimited"); + } else { + result.append(" lo="); + result.append(MyString.describe(lo)); + result.append(" hi="); + result.append(MyString.describe(hi)); + } + + result.append(" tgtV="); + float targetV = motor.getTargetVelocity(); + result.append(MyString.describe(targetV)); + + result.append(" cfm="); + float cfm = motor.getNormalCFM(); + result.append(MyString.describe(cfm)); + + result.append(" damp="); + float damping = motor.getDamping(); + result.append(MyString.describe(damping)); + + result.append(" maxMF="); + float maxMF = motor.getMaxMotorForce(); + result.append(MyString.describe(maxMF)); + + if (hi >= lo) { + result.append(" lim[cfm="); + cfm = motor.getStopCFM(); + result.append(MyString.describe(cfm)); + + result.append(" erp="); + float erp = motor.getERP(); + result.append(MyString.describe(erp)); + + result.append(" maxMF="); + maxMF = motor.getMaxLimitForce(); + result.append(MyString.describe(maxMF)); + + result.append(" rest="); + float rest = motor.getRestitution(); + result.append(MyString.describe(rest)); + + result.append(" soft="); + float soft = motor.getLimitSoftness(); + result.append(MyString.describe(soft)); + result.append(']'); + } + } else { + result.append(" DISABLED"); + } + + return result.toString(); + } + + /** + * Generate a brief textual description for the specified + * PhysicsSoftBody.Material + * + * @param material the Material to describe (not null, unaffected) + * @return description (not null, not empty) + */ + public String describe(SoftBodyMaterial material) { + String result = String.format( + "Material stiffness[ang=%s lin=%s vol=%s]", + MyString.describe(material.angularStiffness()), + MyString.describe(material.linearStiffness()), + MyString.describe(material.volumeStiffness())); + return result; + } + + /** + * Generate a brief textual description for the specified SoftBodyWorldInfo. + * + * @param info the info to describe (not null, unaffected) + * @return description (not null, not empty) + */ + public String describe(SoftBodyWorldInfo info) { + StringBuilder result = new StringBuilder(40); + + result.append("SbwInfo grav["); + Vector3f grav = info.copyGravity(null); + String description = MyVector3f.describe(grav); + result.append(description); + + result.append("] offset="); + float offset = info.waterOffset(); + description = MyString.describe(offset); + result.append(description); + + result.append(" norm["); + Vector3f norm = info.copyWaterNormal(null); + description = MyVector3f.describe(norm); + result.append(description); + + result.append("] water="); + float water = info.waterDensity(); + description = MyString.describe(water); + result.append(description); + + result.append(" air="); + float air = info.airDensity(); + description = MyString.describe(air); + result.append(description); + + result.append(" maxDisp="); + float maxDisp = info.maxDisplacement(); + description = MyString.describe(maxDisp); + result.append(description); + + return result.toString(); + } + + /** + * Describe the indexed axis of the specified TranslationalLimitMotor. + * + * @param motor the motor to describe (not null, unaffected) + * @param axisIndex which axis: 0→X, 1→Y, 2→Z + * @return descriptive text (not null, not empty) + */ + public String describe(TranslationalLimitMotor motor, int axisIndex) { + Validate.axisIndex(axisIndex, "axis index"); + StringBuilder result = new StringBuilder(80); + Vector3f tmpVector = new Vector3f(); + + if (motor.isEnabled(axisIndex)) { + float offset = motor.getOffset(tmpVector).get(axisIndex); + result.append(offset); + + float lo = motor.getLowerLimit(tmpVector).get(axisIndex); + float hi = motor.getUpperLimit(tmpVector).get(axisIndex); + if (hi < lo) { + result.append(" unlimited"); + } else { + result.append(" lo="); + result.append(MyString.describe(lo)); + result.append(" hi="); + result.append(MyString.describe(hi)); + } + + result.append(" tgtV="); + float targetV = motor.getTargetVelocity(tmpVector).get(axisIndex); + result.append(MyString.describe(targetV)); + + result.append(" cfm="); + float cfm = motor.getNormalCFM(tmpVector).get(axisIndex); + result.append(MyString.describe(cfm)); + + result.append(" damp="); + float damping = motor.getDamping(); + result.append(MyString.describe(damping)); + + result.append(" maxMF="); + float maxMF = motor.getMaxMotorForce(tmpVector).get(axisIndex); + result.append(MyString.describe(maxMF)); + + if (hi >= lo) { + result.append(" lim[cfm="); + cfm = motor.getStopCFM(tmpVector).get(axisIndex); + result.append(MyString.describe(cfm)); + + result.append(" erp="); + float erp = motor.getERP(tmpVector).get(axisIndex); + result.append(MyString.describe(erp)); + + result.append(" rest="); + float rest = motor.getRestitution(); + result.append(MyString.describe(rest)); + + result.append(" soft="); + float softness = motor.getLimitSoftness(); + result.append(MyString.describe(softness)); + result.append(']'); + } + } else { + result.append(" DISABLED"); + } + + return result.toString(); + } + + /** + * Describe the specified VehicleWheel. + * + * @param wheel the wheel to describe (not null, unaffected) + * @return descriptive text (not null, not empty) + */ + public String describe(VehicleWheel wheel) { + StringBuilder result = new StringBuilder(80); + + boolean isFront = wheel.isFrontWheel(); + if (isFront) { + result.append("frnt"); + } else { + result.append("rear"); + } + + result.append(" r="); + float r = wheel.getRadius(); + result.append(MyString.describe(r)); + + result.append(" loc["); + Vector3f loc = wheel.getLocation(null); + result.append(MyVector3f.describe(loc)); + + result.append("] axleDir["); + Vector3f axleDir = wheel.getAxle(null); + result.append(MyVector3f.describe(axleDir)); + + result.append("] fSlip="); + float fSlip = wheel.getFrictionSlip(); + result.append(MyString.describe(fSlip)); + + result.append(" rollInf="); + float rollInf = wheel.getRollInfluence(); + result.append(MyString.describe(rollInf)); + + result.append(" sus[damp[co="); + float co = wheel.getWheelsDampingCompression(); + result.append(MyString.describe(co)); + + result.append(" re="); + float re = wheel.getWheelsDampingRelaxation(); + result.append(MyString.describe(re)); + + result.append("] down["); + Vector3f down = wheel.getDirection(null); + result.append(MyVector3f.describe(down)); + + result.append("] maxF="); + float maxF = wheel.getMaxSuspensionForce(); + result.append(MyString.describe(maxF)); + + result.append("] maxTrav="); + float maxTrav = wheel.getMaxSuspensionTravelCm(); + result.append(MyString.describe(maxTrav)); + + result.append(" restL="); + float restL = wheel.getRestLength(); + result.append(MyString.describe(restL)); + + result.append(" stiff="); + float stiff = wheel.getSuspensionStiffness(); + result.append(MyString.describe(stiff)); + + result.append(']'); + + return result.toString(); + } + + /** + * Generate the first line of a textual description for the specified + * SoftBodyConfig. + * + * @param config the config to describe (not null, unaffected) + * @return description (not null, not empty) + */ + public String describe1(SoftBodyConfig config) { + StringBuilder result = new StringBuilder(120); + + result.append("Config aero="); + Aero aeroModel = config.aerodynamics(); + String description = aeroModel.toString(); + result.append(description); + + result.append(" flags="); + int collisionFlags = config.collisionFlags(); + description = ConfigFlag.describe(collisionFlags); + result.append(description); + + description = String.format(" maxVolRatio=%s timeScale=%s velCorr=%s", + MyString.describe(config.get(Sbcp.MaxVolumeRatio)), + MyString.describe(config.get(Sbcp.TimeScale)), + MyString.describe(config.get(Sbcp.VelocityCorrection))); + result.append(description); + + description = String.format(" coef[damp=%s drag=%s fric=%s lift=%s" + + " pose=%s pres=%s volCons=%s]", + MyString.describe(config.get(Sbcp.Damping)), + MyString.describe(config.get(Sbcp.Drag)), + MyString.describe(config.get(Sbcp.DynamicFriction)), + MyString.describe(config.get(Sbcp.Lift)), + MyString.describe(config.get(Sbcp.PoseMatching)), + MyString.describe(config.get(Sbcp.Pressure)), + MyString.describe(config.get(Sbcp.VolumeConservation))); + result.append(description); + + return result.toString(); + } + + /** + * Generate the 2nd line of a brief textual description for the specified + * SoftBodyConfig. + * + * @param config the config to describe (not null, unaffected) + * @return description (not null, not empty) + */ + public String describe2(SoftBodyConfig config) { + StringBuilder result = new StringBuilder(120); + + String description = String.format( + " hardness[a=%s clk=%s clr=%s cls=%s k=%s r=%s s=%s]", + MyString.describe(config.get(Sbcp.AnchorHardness)), + MyString.describe(config.get(Sbcp.ClusterKineticHardness)), + MyString.describe(config.get(Sbcp.ClusterRigidHardness)), + MyString.describe(config.get(Sbcp.ClusterSoftHardness)), + MyString.describe(config.get(Sbcp.KineticHardness)), + MyString.describe(config.get(Sbcp.RigidHardness)), + MyString.describe(config.get(Sbcp.SoftHardness))); + result.append(description); + + description = String.format(" impSplit[clk=%s clr=%s cls=%s]", + MyString.describe(config.get(Sbcp.ClusterKineticSplit)), + MyString.describe(config.get(Sbcp.ClusterRigidSplit)), + MyString.describe(config.get(Sbcp.ClusterSoftSplit))); + result.append(description); + + description = String.format(" iters[cl=%d drift=%d pos=%d vel=%d]", + config.clusterIterations(), + config.driftIterations(), + config.positionIterations(), + config.velocityIterations()); + result.append(description); + + return result.toString(); + } + + /** + * Generate the 2nd line of a textual description for the specified + * VehicleWheel. + * + * @param wheel the wheel to describe (not null, unaffected) + * @return description (not null, not empty) + */ + public String describe2(VehicleWheel wheel) { + StringBuilder result = new StringBuilder(120); + + result.append(" brake="); + float brake = wheel.getBrake(); + result.append(MyString.describe(brake)); + + result.append(" engF="); + float engF = wheel.getEngineForce(); + result.append(MyString.describe(engF)); + + result.append(" steer="); + float steer = wheel.getSteerAngle(); + result.append(MyString.describe(steer)); + + result.append(" susLen="); + float susLen = wheel.getSuspensionLength(); + result.append(MyString.describe(susLen)); + + return result.toString(); + } + + /** + * Describe the angular components of the specified SixDofJoint. + * + * @param joint the joint to describe (not null, unaffected) + * @return descriptive text (not null, not empty) + */ + public String describeAngular(SixDofJoint joint) { + StringBuilder result = new StringBuilder(80); + + result.append("angles["); + Vector3f angles = joint.getAngles(new Vector3f()); + result.append(MyVector3f.describe(angles)); + + result.append("] lo["); // TODO describe axis-by-axis + Vector3f lower = joint.getAngularLowerLimit(new Vector3f()); + result.append(MyVector3f.describe(lower)); + + result.append("] hi["); + Vector3f upper = joint.getAngularUpperLimit(new Vector3f()); + result.append(MyVector3f.describe(upper)); + result.append(']'); + + return result.toString(); + } + + /** + * Describe the application data of a collision object. + * + * @param pco the collision object to describe (not null, unaffected) + * @return a descriptive string (not null, may be empty) + */ + public String describeApplicationData(PhysicsCollisionObject pco) { + Validate.nonNull(pco, "collision object"); + + String result = ""; + Object aData = pco.getApplicationData(); + if (aData != null) { + StringBuilder builder = new StringBuilder(64); + builder.append(" aData="); + appendObjectDescription(builder, aData); + result = builder.toString(); + } + + return result; + } + + /** + * Describe the indexed degree of freedom of the specified New6Dof + * constraint. + * + * @param constraint the constraint to describe (not null, unaffected) + * @param dofIndex which degree of freedom (0→X translation, 1→Y + * translation, 2→Z translation, 3→X rotation, 4→Y rotation, + * 5→Z rotation) + * @return descriptive text (not null, not empty) + */ + public String describeDof(New6Dof constraint, int dofIndex) { + Validate.inRange(dofIndex, "DOF index", 0, 5); + StringBuilder result = new StringBuilder(80); + + float lo = constraint.get(MotorParam.LowerLimit, dofIndex); + float hi = constraint.get(MotorParam.UpperLimit, dofIndex); + if (hi < lo) { + result.append(" free"); + } else if (hi == lo) { + result.append(" lock["); + result.append(MyString.describe(lo)); + result.append(']'); + } else { + result.append(" lims["); + result.append(MyString.describe(lo)); + result.append(' '); + result.append(MyString.describe(hi)); + result.append(']'); + } + + result.append(" motor["); + if (constraint.isMotorEnabled(dofIndex)) { + if (constraint.isServoEnabled(dofIndex)) { // servo motor + result.append("servo target="); + float target = constraint.get(MotorParam.ServoTarget, dofIndex); + result.append(MyString.describe(target)); + result.append(" "); + } + + result.append("tgtV="); + float tgtV = constraint.get(MotorParam.TargetVelocity, dofIndex); + result.append(MyString.describe(tgtV)); + + result.append(" cfm="); + float cfm = constraint.get(MotorParam.MotorCfm, dofIndex); + result.append(MyString.describe(cfm)); + + result.append(" erp="); + float erp = constraint.get(MotorParam.MotorErp, dofIndex); + result.append(MyString.describe(erp)); + + result.append(" maxF="); + float maxF = constraint.get(MotorParam.MaxMotorForce, dofIndex); + result.append(MyString.describe(maxF)); + + } else { + result.append("off"); + } + result.append(']'); + + if (hi >= lo) { // limits/stops are enabled + result.append(" lim[bounce="); + float bounce = constraint.get(MotorParam.Bounce, dofIndex); + result.append(MyString.describe(bounce)); + + result.append(" cfm="); + float cfm = constraint.get(MotorParam.StopCfm, dofIndex); + result.append(MyString.describe(cfm)); + + result.append(" erp="); + float erp = constraint.get(MotorParam.StopErp, dofIndex); + result.append(MyString.describe(erp)); + + result.append(']'); + } + + result.append(" spring["); + if (constraint.isSpringEnabled(dofIndex)) { + result.append("eq="); + float eq = constraint.get(MotorParam.Equilibrium, dofIndex); + result.append(MyString.describe(eq)); + + result.append(" stif="); + float stif = constraint.get(MotorParam.Stiffness, dofIndex); + result.append(MyString.describe(stif)); + + result.append(" damp="); + float damp = constraint.get(MotorParam.Damping, dofIndex); + result.append(MyString.describe(damp)); + } else { + result.append("off"); + } + result.append(']'); + + return result.toString(); + } + + /** + * Briefly describe the collision group and collide-with groups of the + * specified MultiBody. + * + * @param multiBody the object to describe (not null, unaffected) + * @return descriptive text (not null, may be empty) + */ + String describeGroups(MultiBody multiBody) { + StringBuilder result = new StringBuilder(40); + + int group = multiBody.collisionGroup(); + if (group != PhysicsCollisionObject.COLLISION_GROUP_01) { + result.append(" group=0x"); + result.append(Integer.toHexString(group)); + } + + int groupMask = multiBody.collideWithGroups(); + if (groupMask != PhysicsCollisionObject.COLLISION_GROUP_01) { + result.append(" gMask=0x"); + result.append(Integer.toHexString(groupMask)); + } + + return result.toString(); + } + + /** + * Briefly describe the collision group and collide-with groups of the + * specified collision object. + * + * @param pco the object to describe (not null, unaffected) + * @return descriptive text (not null, may be empty) + */ + public String describeGroups(PhysicsCollisionObject pco) { + StringBuilder result = new StringBuilder(40); + + int group = pco.getCollisionGroup(); + if (group != PhysicsCollisionObject.COLLISION_GROUP_01) { + result.append(" group=0x"); + result.append(Integer.toHexString(group)); + } + + int groupMask = pco.getCollideWithGroups(); + if (groupMask != PhysicsCollisionObject.COLLISION_GROUP_01) { + result.append(" gMask=0x"); + result.append(Integer.toHexString(groupMask)); + } + + return result.toString(); + } + + /** + * Describe the specified PhysicsJoint in the context of the specified body. + * + * @param joint the joint to describe (not null, unaffected) + * @param body one end of the joint + * @param forceId true to force inclusion of the PCO native IDs, false to + * include them only when there's no user or application data + * @return descriptive text (not null, not empty) + */ + public String describeJointInBody( + PhysicsJoint joint, PhysicsBody body, boolean forceId) { + StringBuilder result = new StringBuilder(80); + + String desc = describe(joint); + result.append(desc); + + if (joint.countEnds() == 1) { + result.append(" single-ended"); + } else { + result.append(" to"); + PhysicsBody otherBody = joint.findOtherBody(body); + appendPco(result, otherBody, forceId); + } + + JointEnd end = joint.findEnd(body); + if (joint instanceof Constraint) { + Constraint constraint = (Constraint) joint; + result.append(" piv["); + Vector3f piv = constraint.getPivot(end, null); + result.append(MyVector3f.describe(piv)); + result.append(']'); + } + + if (joint instanceof New6Dof) { + New6Dof sixDof = (New6Dof) joint; + result.append(" rot["); + Matrix3f rot = sixDof.getRotationMatrix(end, null); + result.append(MyString.describeMatrix(rot)); + result.append(']'); + } else if (joint instanceof SoftAngularJoint) { + SoftAngularJoint saj = (SoftAngularJoint) joint; + result.append(" axis["); + Vector3f axis = saj.copyAxis(null); + result.append(MyVector3f.describe(axis)); + result.append(']'); + } else if (joint instanceof SoftLinearJoint) { + SoftLinearJoint slj = (SoftLinearJoint) joint; + result.append(" loc["); + Vector3f loc = slj.copyLocation(null); + result.append(MyVector3f.describe(loc)); + result.append(']'); + } + + return result.toString(); + } + + /** + * Describe the specified joint in the context of a PhysicsSpace. + * + * @param joint the joint to describe (not null, unaffected) + * @param forceIds true to force inclusion of PCO native IDs, false to + * include them only when there's no user or application data + * @return descriptive text (not null, not empty) + */ + public String describeJointInSpace(PhysicsJoint joint, boolean forceIds) { + String result; + if (joint instanceof Anchor) { + result = describeAnchorInSpace((Anchor) joint, forceIds); + } else if (joint instanceof Constraint) { + result = describeConstraintInSpace((Constraint) joint, forceIds); + } else { + result = describeSoftJointInSpace( + (SoftPhysicsJoint) joint, forceIds); + } + + return result; + } + + /** + * Describe the linear components of the specified SixDofJoint. + * + * @param joint the joint to describe (not null, unaffected) + * @return descriptive text (not null, not empty) + */ + public String describeLinear(SixDofJoint joint) { + StringBuilder result = new StringBuilder(80); + + result.append("offset["); + Vector3f offset = joint.getPivotOffset(new Vector3f()); + result.append(MyVector3f.describe(offset)); + + result.append("] lo["); // TODO describe axis-by-axis + Vector3f lo = joint.getLinearLowerLimit(new Vector3f()); + result.append(MyVector3f.describe(lo)); + + result.append("] hi["); + Vector3f hi = joint.getLinearUpperLimit(new Vector3f()); + result.append(MyVector3f.describe(hi)); + result.append(']'); + + return result.toString(); + } + + /** + * Describe a collision object in the context of another PCO. + * + * @param pco the object to describe (not null, unaffected) + * @param forceId true to force inclusion of the native ID, false to include + * it only if there's no user or application data + * @return descriptive text (not null, not empty) + */ + String describePco(PhysicsCollisionObject pco, boolean forceId) { + StringBuilder result = new StringBuilder(80); + appendPco(result, pco, forceId); + return result.toString(); + } + + /** + * Describe the user of a collision object. + * + * @param pco the collision object to describe (not null, unaffected) + * @return a descriptive string (not null, may be empty) + */ + public String describeUser(PhysicsCollisionObject pco) { + Validate.nonNull(pco, "collision object"); + + String result = ""; + Object user = pco.getUserObject(); + if (user != null) { + StringBuilder builder = new StringBuilder(64); + builder.append(" user="); + appendObjectDescription(builder, user); + result = builder.toString(); + } + + return result; + } + // ************************************************************************* + // Describer methods + + /** + * Create a copy of this PhysicsDescriber. + * + * @return a new instance, equivalent to this one + * @throws CloneNotSupportedException if the superclass isn't cloneable + */ + @Override + public PhysicsDescriber clone() throws CloneNotSupportedException { + PhysicsDescriber clone = (PhysicsDescriber) super.clone(); + return clone; + } + + /** + * Generate a textual description of a scene-graph control. + * + * @param control (not null) + * @return description (not null) + */ + @Override + protected String describe(Control control) { + Validate.nonNull(control, "control"); + String result = MyControlP.describe(control); + return result; + } + + /** + * Test whether a scene-graph control is enabled. + * + * @param control control to test (not null) + * @return true if the control is enabled, otherwise false + */ + @Override + protected boolean isControlEnabled(Control control) { + Validate.nonNull(control, "control"); + + boolean result = !MyControlP.canDisable(control) + || MyControlP.isEnabled(control); + + return result; + } + // ************************************************************************* + // private methods + + /** + * Append a description of an arbitrary Object, such as a PCO's user. + * + * @param builder the StringBuilder to append to (not null, modified) + * @param subject the Object to describe (not null, unaffected) + */ + private static void appendObjectDescription( + StringBuilder builder, Object subject) { + String className = subject.getClass().getSimpleName(); + + String desc; + if (subject instanceof Material) { + builder.append(className); + desc = ((Material) subject).getName(); + } else if (subject instanceof PhysicsLink) { + builder.append(className); + desc = ((PhysicsLink) subject).boneName(); + } else if (subject instanceof Spatial) { + builder.append(className); + desc = ((Spatial) subject).getName(); + } else if (subject instanceof String) { + builder.append("String"); + desc = (String) subject; + } else { + desc = subject.toString(); + } + + if (desc != null) { + if (desc.length() > 50) { + desc = desc.substring(0, 47) + "..."; + } + builder.append(MyString.quote(desc)); + } + } + + /** + * Append a description of a collision object in the context of another PCO. + * + * @param builder the StringBuilder to append to (not null, modified) + * @param pco the object to describe (not null, unaffected) + * @param forceId true to force inclusion of the native ID, false to include + * it only if there's no user or application data + */ + private void appendPco(StringBuilder builder, PhysicsCollisionObject pco, + boolean forceId) { + String desc; + if (pco.getApplicationData() == null && pco.getUserObject() == null) { + desc = pco.toString(); + builder.append(desc); + + } else { + builder.append('['); + + if (forceId) { + desc = pco.toString(); + } else { + desc = pco.getClass().getSimpleName(); + desc = desc.replace("Body", ""); + desc = desc.replace("Control", "C"); + desc = desc.replace("Physics", ""); + desc = desc.replace("Object", ""); + } + builder.append(desc); + + desc = describeApplicationData(pco); + builder.append(desc); + + desc = describeUser(pco); + builder.append(desc); + + builder.append(']'); + } + + if (!pco.isInWorld()) { + builder.append("_NOT_IN_WORLD"); + } + } + + /** + * Describe the specified Anchor in the context of a PhysicsSpace. + * + * @param anchor the Anchor to describe (not null, unaffected) + * @param forceIds true to force inclusion of the PCO native IDs, false to + * include them only when there's no user or application data + * @return descriptive text (not null, not empty) + */ + private String describeAnchorInSpace(Anchor anchor, boolean forceIds) { + StringBuilder result = new StringBuilder(80); + + String desc = describe(anchor); + result.append(desc); + + result.append(" a="); + PhysicsSoftBody a = anchor.getSoftBody(); + appendPco(result, a, forceIds); + + result.append(" ["); + int nodeIndex = anchor.nodeIndex(); + result.append(nodeIndex); + result.append(']'); + + result.append(" b="); + PhysicsRigidBody b = anchor.getRigidBody(); + appendPco(result, b, forceIds); + + result.append(" piv["); + Vector3f piv = anchor.copyPivot(null); + result.append(MyVector3f.describe(piv)); + result.append(']'); + + result.append(" infl="); + float infl = anchor.influence(); + result.append(MyString.describe(infl)); + + return result.toString(); + } + + /** + * Describe the specified Constraint in the context of a PhysicsSpace. + * + * @param constraint the Constraint to describe (not null, unaffected) + * @param forceIds true to force inclusion of the PCO native IDs, false to + * include them only when there's no user or application data + * @return descriptive text (not null, not empty) + */ + private String describeConstraintInSpace( + Constraint constraint, boolean forceIds) { + StringBuilder result = new StringBuilder(80); + + String desc = describe(constraint); + result.append(desc); + + if (constraint.countEnds() == 2) { + boolean endsCollide = constraint.isCollisionBetweenLinkedBodies(); + if (endsCollide) { + result.append(" collide"); + } else { + result.append(" NOcollide"); + } + } + + int iters = constraint.getOverrideIterations(); + if (iters != -1) { + result.append(" iters="); + result.append(iters); + } + + int numDyn = 0; + PhysicsRigidBody a = constraint.getBodyA(); + if (a != null) { + result.append(" a:"); + appendPco(result, a, forceIds); + if (a.isDynamic()) { + ++numDyn; + } + } + + PhysicsRigidBody b = constraint.getBodyB(); + if (b != null) { + result.append(" b:"); + appendPco(result, b, forceIds); + if (b.isDynamic()) { + ++numDyn; + } + } + + if (numDyn == 0) { + result.append(" NO_DYNAMIC_END"); + } + + if (constraint.isFeedback()) { + float impulse = constraint.getAppliedImpulse(); + result.append(" impulse="); + result.append(impulse); + } + + float bit = constraint.getBreakingImpulseThreshold(); + if (bit != Float.MAX_VALUE) { + result.append(" bit="); + result.append(MyString.describe(bit)); + } + + if (forceIds) { + result.append(" #"); + long id = constraint.nativeId(); + String hex = Long.toHexString(id); + result.append(hex); + } + + return result.toString(); + } + + /** + * Describe the specified height and radius. + * + * @param height the height of the shape + * @param radius the radius of the shape + * @return a bracketed description (not null, not empty) + */ + private static String describeHeightAndRadius(float height, float radius) { + String hText = MyString.describe(height); + String rText = MyString.describe(radius); + String result = String.format(" h=%s r=%s", hText, rText); + + return result; + } + + /** + * Describe the specified soft joint in the context of a PhysicsSpace. + * + * @param joint the soft joint to describe (not null, unaffected) + * @param forceIds true to force inclusion of the PCO native IDs, false to + * include them only when there's no user or application data + * @return descriptive text (not null, not empty) + */ + private String describeSoftJointInSpace( + SoftPhysicsJoint joint, boolean forceIds) { + StringBuilder result = new StringBuilder(80); + + String desc = describe(joint); + result.append(desc); + + PhysicsSoftBody a = joint.getSoftBodyA(); + result.append(" a="); + appendPco(result, a, forceIds); + + result.append(" ["); + int clusterIndex = joint.clusterIndexA(); + result.append(clusterIndex); + result.append(']'); + + PhysicsBody b = joint.getBody(JointEnd.B); + result.append(" b="); + appendPco(result, b, forceIds); + + if (joint.isSoftSoft()) { + result.append(" ["); + clusterIndex = joint.clusterIndexB(); + result.append(clusterIndex); + result.append(']'); + } + + result.append(" cfm="); + float cfm = joint.getCFM(); + result.append(MyString.describe(cfm)); + + result.append(" erp="); + float erp = joint.getERP(); + result.append(MyString.describe(erp)); + + result.append(" split="); + float split = joint.getSplit(); + result.append(MyString.describe(split)); + + return result.toString(); + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDumper.java b/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDumper.java index 51ce6b4d5..aabed7c7b 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDumper.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/PhysicsDumper.java @@ -1,1686 +1,1686 @@ -/* - Copyright (c) 2013-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.app.state.AppState; -import com.jme3.bounding.BoundingBox; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.DeformableSpace; -import com.jme3.bullet.MultiBody; -import com.jme3.bullet.MultiBodyJointType; -import com.jme3.bullet.MultiBodyLink; -import com.jme3.bullet.MultiBodySpace; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RayTestFlag; -import com.jme3.bullet.SoftBodyWorldInfo; -import com.jme3.bullet.SolverInfo; -import com.jme3.bullet.SolverMode; -import com.jme3.bullet.SolverType; -import com.jme3.bullet.collision.Activation; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.GImpactCollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.collision.shapes.SimplexCollisionShape; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.joints.motors.RotationalLimitMotor; -import com.jme3.bullet.joints.motors.TranslationalLimitMotor; -import com.jme3.bullet.objects.MultiBodyCollider; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsCharacter; -import com.jme3.bullet.objects.PhysicsGhostObject; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.PhysicsVehicle; -import com.jme3.bullet.objects.VehicleWheel; -import com.jme3.bullet.objects.infos.Cluster; -import com.jme3.bullet.objects.infos.RigidBodyMotionState; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import java.io.PrintStream; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collection; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.debug.Describer; -import jme3utilities.debug.Dumper; -import jme3utilities.math.MyBuffer; -import jme3utilities.math.MyQuaternion; -import jme3utilities.math.MyVector3f; - -/** - * Dump Minie data structures for debugging purposes. - *

- * The level of detail can be configured dynamically. - * - * @author Stephen Gold sgold@sonic.net - */ -public class PhysicsDumper extends Dumper { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(PhysicsDumper.class.getName()); - /** - * local copy of {@link com.jme3.math.Vector3f#UNIT_XYZ} - */ - final private static Vector3f scaleIdentity = new Vector3f(1f, 1f, 1f); - // ************************************************************************* - // fields - - /** - * enable dumping of children in compound collision shapes - */ - private boolean dumpChildShapes = false; - /** - * enable dumping of clusters in soft bodies - */ - private boolean dumpClustersInSofts = false; - /** - * enable dumping of ignored objects in collision objects - */ - private boolean dumpIgnores = false; - /** - * enable dumping of physics joints in bodies - */ - private boolean dumpJointsInBodies = false; - /** - * enable dumping of joints in physics spaces - */ - private boolean dumpJointsInSpaces = false; - /** - * enable dumping of motors in physics joints - */ - private boolean dumpMotors = false; - /** - * enable dumping native IDs of physics objects - */ - private boolean dumpNativeIDs = false; - /** - * enable dumping of soft-body nodes in clusters - */ - private boolean dumpNodesInClusters = false; - /** - * enable dumping of nodes in soft bodies - */ - private boolean dumpNodesInSofts = false; - /** - * enable dumping of collision objects in physics spaces - */ - private boolean dumpPcos = true; - // ************************************************************************* - // constructors - - /** - * Instantiate a PhysicsDumper that uses {@code System.out} for output. - */ - public PhysicsDumper() { - super(); - PhysicsDescriber newDescriber = new PhysicsDescriber(); - setDescriber(newDescriber); - } - - /** - * Instantiate a PhysicsDumper that uses the specified output stream. - * - * @param printStream output stream (not null) - */ - public PhysicsDumper(PrintStream printStream) { - super(printStream); - PhysicsDescriber newDescriber = new PhysicsDescriber(); - setDescriber(newDescriber); - } - // ************************************************************************* - // new methods exposed - - /** - * Dump the specified BulletAppState. - * - * @param appState the app state to dump (not null, unaffected) - */ - public void dump(BulletAppState appState) { - Validate.nonNull(appState, "app state"); - dumpBas(appState, ""); - } - - /** - * Dump the specified CollisionShape. - * - * @param shape the shape to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(CollisionShape shape, String indent) { - Validate.nonNull(shape, "shape"); - Validate.nonNull(indent, "indent"); - - addLine(indent); - - PhysicsDescriber describer = getDescriber(); - String desc = describer.describe(shape); - stream.print(desc); - - Vector3f scale = shape.getScale(null); - desc = describer.describeScale(scale); - addDescription(desc); - - long objectId = shape.nativeId(); - addNativeId(objectId); - - if (dumpChildShapes && shape instanceof CompoundCollisionShape) { - String moreIndent = indent + indentIncrement(); - dumpChildren((CompoundCollisionShape) shape, moreIndent); - } - } - - /** - * Dump the specified MultiBodyCollider. - * - * @param collider the collider to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(MultiBodyCollider collider, String indent) { - Validate.nonNull(collider, "collider"); - Validate.nonNull(indent, "indent"); - - addLine(indent); - stream.print("Collider"); - - PhysicsDescriber describer = getDescriber(); - String desc = describer.describeApplicationData(collider); - stream.print(desc); - desc = describer.describeUser(collider); - stream.print(desc); - - if (!collider.isActive()) { - stream.print("/inactive"); - } - if (!collider.isContactResponse()) { - stream.print("/NOresponse"); - } - if (!collider.isInWorld()) { - stream.print("/NOspace"); - } - - float mass = collider.mass(); - String massText = MyString.describe(mass); - stream.printf(" mass=%s", massText); - - Vector3f loc = collider.getPhysicsLocation(null); - stream.printf(" loc[%s]", MyVector3f.describe(loc)); - - desc = describer.describeGroups(collider); - stream.print(desc); - - long objectId = collider.nativeId(); - addNativeId(objectId); - /* - * The 2nd line has the shape and scale. - * There may be additional lines for child shapes. - */ - CollisionShape shape = collider.getCollisionShape(); - dump(shape, indent + " "); - // TODO ignored objects - } - - /** - * Dump the specified PhysicsCharacter. - * - * @param character the character to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(PhysicsCharacter character, String indent) { - Validate.nonNull(character, "character"); - Validate.nonNull(indent, "indent"); - - stream.printf("%n%sCharacter", indent); - - PhysicsDescriber describer = getDescriber(); - String desc = describer.describeApplicationData(character); - stream.print(desc); - desc = describer.describeUser(character); - stream.print(desc); - - Vector3f location = character.getPhysicsLocation(null); - String locString = MyVector3f.describe(location); - stream.printf(" loc[%s]", locString); - - Vector3f walk = character.getWalkDirection(null); - stream.printf(" walk[%s]", MyVector3f.describeDirection(walk)); - - Vector3f lin = character.getLinearVelocity(null); - stream.printf(" v[%s]", MyVector3f.describe(lin)); - - Vector3f ang = character.getAngularVelocity(null); - stream.printf(" angV[%s]", MyVector3f.describe(ang)); - - long objectId = character.nativeId(); - addNativeId(objectId); - - // The 2nd line has the character's configuration. - addLine(indent); - Vector3f grav = character.getGravity(null); - stream.printf(" grav[%s]", MyVector3f.describe(grav)); - - Vector3f up = character.getUpDirection(null); - stream.printf(" up[%s]", MyVector3f.describeDirection(up)); - - stream.print(" jumpSp="); - float jump = character.getJumpSpeed(); - stream.print(MyString.describe(jump)); - - float angularDamping = character.getAngularDamping(); - float linearDamping = character.getLinearDamping(); - stream.print("] damp[l="); - stream.print(MyString.describe(linearDamping)); - stream.print(" a="); - stream.print(MyString.describe(angularDamping)); - - stream.print("] max[fallSp="); - float fall = character.getFallSpeed(); - stream.print(MyString.describe(fall)); - - stream.print(" pen="); - float maxPen = character.getMaxPenetrationDepth(); - stream.print(MyString.describe(maxPen)); - - stream.print(" slope="); - float maxSlope = character.getMaxSlope(); - stream.print(MyString.describe(maxSlope)); - - stream.print(" stepHt="); - float maxStepHt = character.getStepHeight(); - stream.print(MyString.describe(maxStepHt)); - stream.print("] "); - - boolean gsTest = character.isUsingGhostSweepTest(); - if (!gsTest) { - stream.print("NO"); - } - stream.print("gsTest"); - - desc = describer.describeGroups(character); - stream.print(desc); - /* - * The 3rd line has the shape and scale. - * There may be additional lines for child shapes. - */ - CollisionShape shape = character.getCollisionShape(); - dump(shape, indent + " "); - - addLine(indent); - int numIgnores = character.countIgnored(); - stream.printf( - " with %d ignore%s", numIgnores, (numIgnores == 1) ? "" : "s"); - if (dumpIgnores && numIgnores > 0) { - dumpIgnores(character, indent); - } - } - - /** - * Dump the specified PhysicsGhostObject. - * - * @param ghost the ghost object to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(PhysicsGhostObject ghost, String indent) { - Validate.nonNull(ghost, "ghost"); - Validate.nonNull(indent, "indent"); - - stream.printf("%n%sGhost", indent); - - PhysicsDescriber describer = getDescriber(); - String desc = describer.describeApplicationData(ghost); - stream.print(desc); - desc = describer.describeUser(ghost); - stream.print(desc); - - Vector3f location = ghost.getPhysicsLocation(null); - String locString = MyVector3f.describe(location); - stream.printf(" loc[%s]", locString); - - Quaternion orientation = ghost.getPhysicsRotation(null); - if (!MyQuaternion.isRotationIdentity(orientation)) { - String orientText = MyQuaternion.describe(orientation); - stream.printf(" orient[%s]", orientText); - } - - long objectId = ghost.nativeId(); - addNativeId(objectId); - /* - * The 2nd line has the shape and scale. - * There may be additional lines for child shapes. - */ - CollisionShape shape = ghost.getCollisionShape(); - dump(shape, indent + " "); - /* - * The next line has the bounding box, group info, - * and number of ignores. - */ - addLine(indent); - if (shape instanceof CompoundCollisionShape - || shape instanceof GImpactCollisionShape - || shape instanceof HeightfieldCollisionShape - || shape instanceof HullCollisionShape - || shape instanceof MeshCollisionShape - || shape instanceof SimplexCollisionShape) { - BoundingBox aabb = shape.boundingBox(location, orientation, null); - desc = describer.describe(aabb); - stream.printf(" aabb[%s]", desc); - } - - desc = describer.describeGroups(ghost); - stream.print(desc); - - int numIgnores = ghost.countIgnored(); - stream.printf( - " with %d ignore%s", numIgnores, (numIgnores == 1) ? "" : "s"); - if (dumpIgnores && numIgnores > 0) { - dumpIgnores(ghost, indent); - } - } - - /** - * Dump the specified PhysicsJoint in a PhysicsSpace context. - * - * @param joint the joint to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(PhysicsJoint joint, String indent) { - Validate.nonNull(joint, "joint"); - Validate.nonNull(indent, "indent"); - - String moreIndent = indent + indentIncrement(); - addLine(moreIndent); - PhysicsDescriber describer = getDescriber(); - String desc = describer.describeJointInSpace(joint, dumpNativeIDs); - stream.print(desc); - - String mmIndent = moreIndent + indentIncrement(); - if (joint instanceof SixDofJoint) { - SixDofJoint sixDof = (SixDofJoint) joint; - - desc = describer.describeAngular(sixDof); - stream.printf("%n%s %s", moreIndent, desc); - desc = describer.describeLinear(sixDof); - stream.printf("%n%s %s", moreIndent, desc); - - if (dumpMotors) { - for (int axisI = 0; axisI < MyVector3f.numAxes; ++axisI) { - String axisName = MyString.axisName(axisI); - stream.printf("%n%srot%s: ", mmIndent, axisName); - RotationalLimitMotor motor - = sixDof.getRotationalLimitMotor(axisI); - desc = describer.describe(motor); - stream.print(desc); - } - - TranslationalLimitMotor motor - = sixDof.getTranslationalLimitMotor(); - for (int axisI = 0; axisI < MyVector3f.numAxes; ++axisI) { - String axisName = MyString.axisName(axisI); - stream.printf("%n%stra%s: ", mmIndent, axisName); - desc = describer.describe(motor, axisI); - stream.print(desc); - } - } - - } else if (joint instanceof New6Dof) { - New6Dof sixDof = (New6Dof) joint; - - addLine(moreIndent); - Vector3f offset = sixDof.getPivotOffset(null); - stream.printf(" offset[%s]", MyVector3f.describe(offset)); - Vector3f locA = sixDof.calculatedOriginA(null); - stream.printf(" locA[%s]", MyVector3f.describe(locA)); - Vector3f locB = sixDof.calculatedOriginB(null); - stream.printf(" locB[%s]", MyVector3f.describe(locB)); - - addLine(moreIndent); - Vector3f angles = sixDof.getAngles(null); - stream.printf(" angles[%s]", MyVector3f.describe(angles)); - desc = sixDof.getRotationOrder().toString(); - stream.printf(" ro=%s", desc); - Matrix3f basA = sixDof.calculatedBasisA(null); - stream.printf(" basA[%s]", MyString.describeMatrix(basA)); - Matrix3f basB = sixDof.calculatedBasisB(null); - stream.printf(" basB[%s]", MyString.describeMatrix(basB)); - - if (dumpMotors) { - for (int dofIndex = 0; dofIndex < 6; ++dofIndex) { - int axisIndex = dofIndex % MyVector3f.numAxes; - String tr = (dofIndex < 3) ? "T" : "R"; - String axisName = MyString.axisName(axisIndex); - stream.printf("%n%s%s%s:", mmIndent, tr, axisName); - desc = describer.describeDof(sixDof, dofIndex); - stream.print(desc); - } - } - } - } - - /** - * Dump the specified PhysicsRigidBody. - * - * @param body the rigid body to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(PhysicsRigidBody body, String indent) { - Validate.nonNull(body, "body"); - Validate.nonNull(indent, "indent"); - - addLine(indent); - if (body instanceof PhysicsVehicle) { - stream.print("Vehicle "); - } else { - stream.print("Rigid "); - } - - String desc = MyPco.describe(body); - stream.print(desc); - - PhysicsDescriber describer = getDescriber(); - desc = describer.describeApplicationData(body); - stream.print(desc); - desc = describer.describeUser(body); - stream.print(desc); - - RigidBodyMotionState motionState = body.getMotionState(); - Vector3f msLoc = motionState.getLocation(null); - String locString = MyVector3f.describe(msLoc); - stream.printf(" msLoc[%s]", locString); - Vector3f location = body.getPhysicsLocation(null); - if (!location.equals(msLoc)) { - locString = MyVector3f.describe(location); - stream.printf(" loc[%s]", locString); - } - - Quaternion orientation = body.getPhysicsRotation(null); - if (!MyQuaternion.isRotationIdentity(orientation)) { - String orientText = MyQuaternion.describe(orientation); - stream.printf(" orient[%s]", orientText); - } - - long objectId = body.nativeId(); - addNativeId(objectId); - - // 2nd line: activation state and contact parameters - addLine(indent); - addActivationState(body); - addContactParameters(body); - - if (body.isDynamic()) { - // The next 3 lines describes the dynamic properties. - addDynamicProperties(body, indent); - } - /* - * The next line has the shape and scale. - * There may be additional lines for child shapes. - */ - CollisionShape shape = body.getCollisionShape(); - dump(shape, indent + " "); - /* - * The next line has the bounding box, group info, number of wheels, - * and number of ignores/joints. - */ - addLine(indent); - if (shape instanceof CompoundCollisionShape - || shape instanceof GImpactCollisionShape - || shape instanceof HeightfieldCollisionShape - || shape instanceof HullCollisionShape - || shape instanceof MeshCollisionShape - || shape instanceof SimplexCollisionShape) { - BoundingBox aabb = shape.boundingBox(location, orientation, null); - desc = describer.describe(aabb); - stream.printf(" aabb[%s]", desc); - } - - desc = describer.describeGroups(body); - stream.print(desc); - - stream.print(" with"); - if (body instanceof PhysicsVehicle) { - PhysicsVehicle vehicle = (PhysicsVehicle) body; - int numWheels = vehicle.getNumWheels(); - stream.printf( - " %d wheel%s", numWheels, (numWheels == 1) ? "" : "s"); - if (numWheels > 0) { - dumpWheels(vehicle, indent, numWheels); - } else { - stream.print(','); - } - } - - int numIgnores = body.countIgnored(); - stream.printf(" %d ignore%s", numIgnores, (numIgnores == 1) ? "" : "s"); - if (dumpIgnores && numIgnores > 0) { - dumpIgnores(body, indent); - } - - int numJoints = body.countJoints(); - stream.printf( - " and %d joint%s", numJoints, (numJoints == 1) ? "" : "s"); - if (dumpJointsInBodies && numJoints > 0) { - dumpJoints(body, indent); - } - } - - /** - * Dump the specified PhysicsSoftBody. - * - * @param body the soft body to dump (not null, unaffected) - * @param indent (not null) - */ - public void dump(PhysicsSoftBody body, String indent) { - Validate.nonNull(body, "body"); - Validate.nonNull(indent, "indent"); - - stream.printf("%n%sSoft ", indent); - - PhysicsDescriber describer = getDescriber(); - BoundingBox aabb = body.boundingBox(null); - String desc = describer.describe(aabb); - stream.print(desc); - - stream.print(" mass="); - float mass = body.getMass(); - desc = MyString.describe(mass); - stream.print(desc); - - stream.print(" marg="); - float margin = body.margin(); - desc = MyString.describe(margin); - stream.print(desc); - - long objectId = body.nativeId(); - addNativeId(objectId); - - stream.printf("%n%s vol=", indent); - float volume = body.volume(); - desc = MyString.describe(volume); - stream.print(desc); - - stream.print(" wind["); - Vector3f wind = body.windVelocity(null); - desc = MyVector3f.describe(wind); - stream.print(desc); - stream.print(']'); - - desc = describer.describeApplicationData(body); - stream.print(desc); - desc = describer.describeUser(body); - stream.print(desc); - - int numLinks = body.countLinks(); - int numFaces = body.countFaces(); - int numTetras = body.countTetras(); - stream.printf(" with %d link%s, %d face%s, %d tetra%s", - numLinks, (numLinks == 1) ? "" : "s", - numFaces, (numFaces == 1) ? "" : "s", - numTetras, (numTetras == 1) ? "" : "s"); - - Quaternion orientation = body.getPhysicsRotation(null); - if (!MyQuaternion.isRotationIdentity(orientation)) { - desc = MyQuaternion.describe(orientation); - stream.printf(" orient[%s]", desc); - } - - // 3rd & 4th lines describe the config. - SoftBodyConfig config = body.getSoftConfig(); - desc = describer.describe1(config); - stream.printf("%n%s %s", indent, desc); - desc = describer.describe2(config); - stream.printf("%n%s %s", indent, desc); - - // 5th line describes the material. - SoftBodyMaterial material = body.getSoftMaterial(); - desc = describer.describe(material); - stream.printf("%n%s %s", indent, desc); - - // 6th line describes the world info. - SoftBodyWorldInfo info = body.getWorldInfo(); - desc = describer.describe(info); - stream.printf("%n%s %s ", indent, desc); - if (!body.isWorldInfoProtected()) { - stream.print("NOT"); - } - stream.print("protected"); - objectId = info.nativeId(); - addNativeId(objectId); - - // 7th line describes the group info and number of anchors. - desc = describer.describeGroups(body); - stream.printf("%n%s%s", indent, desc); - - // physics joints in the soft body - int numJoints = body.countJoints(); - stream.printf( - " with %d joint%s", numJoints, (numJoints == 1) ? "" : "s"); - if (dumpJointsInBodies && numJoints > 0) { - dumpJoints(body, indent); - addLine(indent); - } else { - stream.print(','); - } - - // clusters in the soft body - int numClusters = body.countClusters(); - stream.printf( - " %d cluster%s", numClusters, (numClusters == 1) ? "" : "s"); - if (dumpClustersInSofts && numClusters > 0) { - dumpClusters(body, indent); - } else { - stream.print(','); - } - - // nodes in the soft body - int numNodes = body.countNodes(); - stream.printf(" %d node%s", numNodes, (numNodes == 1) ? "" : "s"); - int numPinned = body.countPinnedNodes(); - if (numPinned > 0) { - stream.printf(" (%d pinned)", numPinned); - } - if (dumpNodesInSofts && numNodes > 0) { - dumpNodes(body, indent); - } - // TODO ignored objects - } - - /** - * Dump the specified PhysicsSpace. - * - * @param space the PhysicsSpace to dump (not null, unaffected) - */ - public void dump(PhysicsSpace space) { - dump(space, "", null); - } - - /** - * Dump the specified PhysicsSpace with the specified filter. TODO dump a - * CollisionSpace - * - * @param space the PhysicsSpace to dump (not null, unaffected) - * @param indent (not null, may be empty) - * @param filter determines which physics objects are dumped, or null to - * dump all (unaffected) - */ - public void dump(PhysicsSpace space, String indent, - BulletDebugAppState.DebugAppStateFilter filter) { - Validate.nonNull(indent, "indent"); - - String type = space.getClass().getSimpleName(); - Collection characters = space.getCharacterList(); - int numCharacters = characters.size(); - Collection ghosts = space.getGhostObjectList(); - int numGhosts = ghosts.size(); - stream.printf("%n%s%s with %d char%s, %d ghost%s, ", - indent, type, numCharacters, (numCharacters == 1) ? "" : "s", - numGhosts, (numGhosts == 1) ? "" : "s"); - - Collection joints = space.getJointList(); - int numJoints = joints.size(); - stream.printf("%d joint%s, ", numJoints, (numJoints == 1) ? "" : "s"); - - Collection multibodies = new ArrayList<>(0); - if (space instanceof MultiBodySpace) { - multibodies = ((MultiBodySpace) space).getMultiBodyList(); - int numMultis = multibodies.size(); - stream.printf( - "%d multi%s, ", numMultis, (numMultis == 1) ? "" : "s"); - } - - Collection rigidBodies = space.getRigidBodyList(); - int numRigids = rigidBodies.size(); - stream.printf("%d rigid%s, ", numRigids, (numRigids == 1) ? "" : "s"); - - Collection softBodies = new ArrayList<>(0); - if (space instanceof PhysicsSoftSpace) { - softBodies = ((PhysicsSoftSpace) space).getSoftBodyList(); - int numSofts = softBodies.size(); - stream.printf("%d soft%s, ", numSofts, (numSofts == 1) ? "" : "s"); - } else if (space instanceof DeformableSpace) { - softBodies = ((DeformableSpace) space).getSoftBodyList(); - int numSofts = softBodies.size(); - stream.printf("%d soft%s, ", numSofts, (numSofts == 1) ? "" : "s"); - } - - int numVehicles = space.getVehicleList().size(); - stream.printf("%d vehicle%s", numVehicles, - (numVehicles == 1) ? "" : "s"); - - long spaceId = space.nativeId(); - addNativeId(spaceId); - - // 2nd line - addLine(indent); - PhysicsSpace.BroadphaseType bphase = space.getBroadphaseType(); - stream.printf(" bphase=%s", bphase); - - Vector3f grav = space.getGravity(null); - stream.printf(" grav[%s] timeStep[", MyVector3f.describe(grav)); - int maxSS = space.maxSubSteps(); - if (maxSS == 0) { - float maxTimeStep = space.maxTimeStep(); - String mtsDesc = MyString.describe(maxTimeStep); - stream.printf("VAR max=%s", mtsDesc); - } else { - float accuracy = space.getAccuracy(); - String accuDesc = MyString.describe(accuracy); - stream.printf("%s maxSS=%d", accuDesc, maxSS); - } - - int cCount = space.countCollisionListeners(); - int cgCount = space.countCollisionGroupListeners(); - int tCount = space.countTickListeners(); - stream.printf("] listeners[c=%d cg=%d t=%d]", cCount, cgCount, tCount); - - // 3rd line: solver type and info - addLine(indent); - SolverType solverType = space.getSolverType(); - SolverInfo solverInfo = space.getSolverInfo(); - int iters = solverInfo.numIterations(); - float cfm = solverInfo.globalCfm(); - stream.printf(" solver[%s iters=%d cfm=%s", - solverType, iters, MyString.describe(cfm)); - int batch = solverInfo.minBatch(); - stream.printf(" batch=%d splitImp[th=", batch); - boolean enabledGlobally = solverInfo.isSplitImpulseEnabled(); - if (enabledGlobally) { - stream.print("global"); - } else { - float th = solverInfo.splitImpulseThreshold(); - stream.print(MyString.describe(th)); - } - float erp = solverInfo.splitImpulseErp(); - stream.printf(" erp=%s]", MyString.describe(erp)); - int mode = solverInfo.mode(); - stream.printf(" mode=%s]", SolverMode.describe(mode)); - - // 4th line: use flags, raytest flags, and world extent - addLine(indent); - if (space.isCcdWithStaticOnly()) { - stream.print(" CCDwso"); - } - if (space.isUsingDeterministicDispatch()) { - stream.print(" DeterministicDispatch"); - } - if (space.isUsingScr()) { - stream.print(" SCR"); - } - int rayTestFlags = space.getRayTestFlags(); - String rayTest = RayTestFlag.describe(rayTestFlags); - stream.printf(" rayTest=%s", rayTest); - - if (bphase == PhysicsSpace.BroadphaseType.AXIS_SWEEP_3 - || bphase == PhysicsSpace.BroadphaseType.AXIS_SWEEP_3_32) { - Vector3f worldMin = space.getWorldMin(null); - String minDesc = MyVector3f.describe(worldMin); - Vector3f worldMax = space.getWorldMax(null); - String maxDesc = MyVector3f.describe(worldMax); - stream.printf(" worldMin[%s] worldMax[%s]", minDesc, maxDesc); - } - - // For soft spaces, 5th line has the world info. - PhysicsDescriber describer = getDescriber(); - if (space instanceof PhysicsSoftSpace) { - SoftBodyWorldInfo info = ((PhysicsSoftSpace) space).getWorldInfo(); - String infoDesc = describer.describe(info); - stream.printf("%n%s %s", indent, infoDesc); - long objectId = info.nativeId(); - addNativeId(objectId); - } - - if (dumpPcos) { - String moreIndent = indent + indentIncrement(); - for (PhysicsCharacter character : characters) { - if (filter == null || filter.displayObject(character)) { - dump(character, moreIndent); - } - } - for (PhysicsGhostObject ghost : ghosts) { - if (filter == null || filter.displayObject(ghost)) { - dump(ghost, moreIndent); - } - } - for (MultiBody multibody : multibodies) { - dumpMultiBody(multibody, moreIndent, filter); - } - for (PhysicsRigidBody rigid : rigidBodies) { - if (filter == null || filter.displayObject(rigid)) { - dump(rigid, moreIndent); - } - } - for (PhysicsSoftBody soft : softBodies) { - if (filter == null || filter.displayObject(soft)) { - dump(soft, moreIndent); - } - } - } - - if (dumpJointsInSpaces) { - dumpJoints(joints, indent, filter); - } - - stream.println(); - } - - /** - * Dump the specified BulletAppState. - * - * @param appState the app state to dump (not null, unaffected) - * @param indent (not null) - */ - public void dumpBas(BulletAppState appState, String indent) { - Validate.nonNull(indent, "indent"); - - String className = appState.getClass().getSimpleName(); - stream.print(className); - - if (appState.isEnabled()) { - stream.print(" enabled "); - - if (!appState.isDebugEnabled()) { - stream.print("NO"); - } - stream.print("debug "); - - float speed = appState.getSpeed(); - String speedString = MyString.describe(speed); - stream.printf("speed=%s", speedString); - - PhysicsSpace.BroadphaseType broadphaseType - = appState.getBroadphaseType(); - stream.printf(" bphase=%s", broadphaseType); - - PhysicsSpace space = appState.getPhysicsSpace(); - String moreIndent = indent + indentIncrement(); - dump(space, moreIndent, null); - } else { - stream.println(" disabled"); - } - } - - /** - * Test whether the specified dump flag is set. - * - * @param dumpFlag which flag to test (not null) - * @return true if output is enabled, otherwise false - */ - public boolean isEnabled(DumpFlags dumpFlag) { - boolean result; - - switch (dumpFlag) { - case BoundsInSpatials: - result = isDumpBounds(); - break; - - case Buckets: - result = isDumpBucket(); - break; - - case ChildShapes: - result = dumpChildShapes; - break; - - case ClustersInSofts: - result = dumpClustersInSofts; - break; - - case CullHints: - result = isDumpCull(); - break; - - case Ignores: - result = dumpIgnores; - break; - - case JointsInBodies: - result = dumpJointsInBodies; - break; - - case JointsInSpaces: - result = dumpJointsInSpaces; - break; - - case MatParams: - result = isDumpMatParam(); - break; - - case Motors: - result = dumpMotors; - break; - - case NativeIDs: - result = dumpNativeIDs; - break; - - case NodesInClusters: - result = dumpNodesInClusters; - break; - - case NodesInSofts: - result = dumpNodesInSofts; - break; - - case Overrides: - result = isDumpOverride(); - break; - - case Pcos: - result = dumpPcos; - break; - - case ShadowModes: - result = isDumpShadow(); - break; - - case Transforms: - result = isDumpTransform(); - break; - - case UserData: - result = isDumpUser(); - break; - - case VertexData: - result = isDumpVertex(); - break; - - default: - throw new IllegalArgumentException("dumpFlag = " + dumpFlag); - } - - return result; - } - - /** - * Configure the specified dump flag. - * - * @param dumpFlag which flag to set (not null) - * @param newValue true to enable output, false to disable it - * @return this instance for chaining - */ - public PhysicsDumper setEnabled(DumpFlags dumpFlag, boolean newValue) { - switch (dumpFlag) { - case BoundsInSpatials: - setDumpBounds(newValue); - break; - - case Buckets: - setDumpBucket(newValue); - break; - - case ChildShapes: - this.dumpChildShapes = newValue; - break; - - case ClustersInSofts: - this.dumpClustersInSofts = newValue; - break; - - case CullHints: - setDumpCull(newValue); - break; - - case Ignores: - this.dumpIgnores = newValue; - break; - - case JointsInBodies: - this.dumpJointsInBodies = newValue; - break; - - case JointsInSpaces: - this.dumpJointsInSpaces = newValue; - break; - - case MatParams: - setDumpMatParam(newValue); - break; - - case Motors: - this.dumpMotors = newValue; - break; - - case NativeIDs: - this.dumpNativeIDs = newValue; - break; - - case NodesInClusters: - this.dumpNodesInClusters = newValue; - break; - - case NodesInSofts: - this.dumpNodesInSofts = newValue; - break; - - case Overrides: - setDumpOverride(newValue); - break; - - case Pcos: - this.dumpPcos = newValue; - break; - - case ShadowModes: - setDumpShadow(newValue); - break; - - case Transforms: - setDumpTransform(newValue); - break; - - case UserData: - setDumpUser(newValue); - break; - - case VertexData: - setDumpVertex(newValue); - break; - - default: - throw new IllegalArgumentException("dumpFlag = " + dumpFlag); - } - - return this; - } - // ************************************************************************* - // Dumper methods - - /** - * Create a deep copy of this dumper. - * - * @return a new instance, equivalent to this one, with its own Describer - * @throws CloneNotSupportedException if the superclass isn't cloneable - */ - @Override - public PhysicsDumper clone() throws CloneNotSupportedException { - PhysicsDumper clone = (PhysicsDumper) super.clone(); - return clone; - } - - /** - * Dump the specified AppState. - * - * @param appState the AppState to dump (not null, unaffected) - * @param indent (not null) - */ - @Override - public void dump(AppState appState, String indent) { - Validate.nonNull(appState, "app state"); - Validate.nonNull(indent, "indent"); - - if (appState instanceof BulletAppState) { - dumpBas((BulletAppState) appState, indent); - } else { - super.dump(appState, indent); - } - } - - /** - * Access the Describer used by this Dumper. - * - * @return the pre-existing instance (not null) - */ - @Override - public PhysicsDescriber getDescriber() { - Describer describer = super.getDescriber(); - PhysicsDescriber result = (PhysicsDescriber) describer; - - return result; - } - // ************************************************************************* - // private methods - - /** - * Print the activation state of the specified rigid body, unless it happens - * to be in the expected state. - * - * @param body (not null, unaffected) - */ - private void addActivationState(PhysicsRigidBody body) { - int expectedState; - if (body.isKinematic() || body instanceof PhysicsVehicle) { - expectedState = Activation.exempt; - } else if (body.isActive()) { - expectedState = Activation.active; - } else { - expectedState = Activation.sleeping; - } - - int activationState = body.getActivationState(); - if (activationState != expectedState) { - stream.printf(" act=%d", activationState); - } - } - - /** - * Print the contact parameters of the specified rigid body. - * - * @param body (not null, unaffected) - */ - private void addContactParameters(PhysicsRigidBody body) { - float fric = body.getFriction(); - stream.print(" contact[fric="); - stream.print(MyString.describe(fric)); - - float rest = body.getRestitution(); - stream.print(" rest="); - stream.print(MyString.describe(rest)); - - float damp = body.getContactDamping(); - stream.print(" damp="); - stream.print(MyString.describe(damp)); - - float pth = body.getContactProcessingThreshold(); - stream.print(" pth="); - stream.print(MyString.describe(pth)); - - float stiff = body.getContactStiffness(); - stream.print(" stiff="); - stream.print(MyString.describe(stiff)); - stream.print(']'); - } - - /** - * Print dynamic properties of the specified rigid body. - * - * @param rigidBody (not null, unaffected) - * @param indent (not null) - */ - private void addDynamicProperties(PhysicsRigidBody rigidBody, - String indent) { - // first line: gravity, CCD, damping, and sleep/activation - addLine(indent); - - Vector3f gravity = rigidBody.getGravity(null); - String graString = MyVector3f.describe(gravity); - stream.printf(" grav[%s] ", graString); - - if (!rigidBody.isGravityProtected()) { - stream.print("NOT"); - } - stream.print("protected ccd[mth="); - float ccdMt = rigidBody.getCcdMotionThreshold(); - stream.print(MyString.describe(ccdMt)); - if (ccdMt > 0f) { - stream.print(" r="); - float ccdR = rigidBody.getCcdSweptSphereRadius(); - stream.print(MyString.describe(ccdR)); - } - - float angularDamping = rigidBody.getAngularDamping(); - float linearDamping = rigidBody.getLinearDamping(); - stream.print("] damp[l="); - stream.print(MyString.describe(linearDamping)); - stream.print(" a="); - stream.print(MyString.describe(angularDamping)); - - float linearThreshold = rigidBody.getLinearSleepingThreshold(); - float angularThreshold = rigidBody.getAngularSleepingThreshold(); - stream.print("] sleep[lth="); - stream.print(MyString.describe(linearThreshold)); - stream.print(" ath="); - stream.print(MyString.describe(angularThreshold)); - if (rigidBody.isActive()) { - float deactivationTime = rigidBody.getDeactivationTime(); - stream.print(" time="); - stream.print(MyString.describe(deactivationTime)); - } - stream.print(']'); - - // 2nd line: linear velocity, applied force, linear factor - addLine(indent); - - Vector3f v = rigidBody.getLinearVelocity(null); - stream.printf(" v[%s]", MyVector3f.describe(v)); - Vector3f force = rigidBody.totalAppliedForce(null); - stream.printf(" force[%s]", MyVector3f.describe(force)); - Vector3f lFact = rigidBody.getLinearFactor(null); - stream.printf(" lFact[%s]", MyVector3f.describe(lFact)); - - // 3rd line: inertia, angular velocity, applied torque, angular factor - addLine(indent); - - stream.print(" inert["); - Vector3f iiLocal = rigidBody.getInverseInertiaLocal(null); - Vector3f inert = scaleIdentity.divide(iiLocal); - stream.print(MyVector3f.describe(inert)); - stream.print(']'); - - Vector3f angularVelocity = rigidBody.getAngularVelocity(null); - stream.printf(" w[%s]", MyVector3f.describe(angularVelocity)); - Vector3f torq = rigidBody.totalAppliedTorque(null); - stream.printf(" torq[%s]", MyVector3f.describe(torq)); - Vector3f aFact = rigidBody.getAngularFactor(null); - stream.printf(" aFact[%s]", MyVector3f.describe(aFact)); - } - - /** - * Add a native ID, if the flag is set. - * - * @param id the unique identifier (not zero) - */ - private void addNativeId(long id) { - if (dumpNativeIDs) { - stream.print(" #"); - String hex = Long.toHexString(id); - stream.print(hex); - } - } - - /** - * Generate a textual description of the indexed vector in the specified - * FloatBuffer. - * - * @param buffer the buffer to read (not null, unaffected) - * @param vectorIndex the index of the vector in the buffer (≥0) - * @return descriptive text (not null, not empty) - */ - private static String describeVector(FloatBuffer buffer, int vectorIndex) { - Vector3f vector = new Vector3f(); - MyBuffer.get(buffer, MyVector3f.numAxes * vectorIndex, vector); - String locString = MyVector3f.describe(vector); - - return locString; - } - - /** - * Dump all children of the specified CompoundCollisionShape. - * - * @param parent the shape to dump (not null, unaffected) - * @param indent (not null) - */ - private void dumpChildren(CompoundCollisionShape parent, String indent) { - PhysicsDescriber describer = getDescriber(); - ChildCollisionShape[] children = parent.listChildren(); - for (ChildCollisionShape child : children) { - addLine(indent); - CollisionShape baseShape = child.getShape(); - String desc = describer.describe(baseShape); - stream.print(desc); - - Vector3f offset = child.copyOffset(null); - if (!MyVector3f.isZero(offset)) { - stream.print(" offset["); - desc = MyVector3f.describe(offset); - stream.print(desc); - stream.print(']'); - } - - Quaternion rot = child.copyRotation(null); - if (!MyQuaternion.isRotationIdentity(rot)) { - stream.print(" rot["); - desc = MyQuaternion.describe(rot); - stream.print(desc); - stream.print(']'); - } - - Vector3f scale = baseShape.getScale(null); - desc = describer.describeScale(scale); - addDescription(desc); - - long objectId = baseShape.nativeId(); - addNativeId(objectId); - } - } - - /** - * Dump all clusters in the specified soft body. - * - * @param softBody the body to dump (not null, unaffected) - * @param indent (not null) - */ - private void dumpClusters(PhysicsSoftBody softBody, String indent) { - stream.print(':'); - FloatBuffer coms = softBody.copyClusterCenters(null); - FloatBuffer masses = softBody.copyClusterMasses(null); - int numClusters = softBody.countClusters(); - for (int clusterIndex = 0; clusterIndex < numClusters; ++clusterIndex) { - stream.printf("%n%s [%d] com[", indent, clusterIndex); - String desc = describeVector(coms, clusterIndex); - stream.print(desc); - - stream.print("] mass="); - float mass = masses.get(clusterIndex); - stream.print(MyString.describe(mass)); - - stream.print(" damp[ang="); - float angularDamping - = softBody.get(Cluster.AngularDamping, clusterIndex); - stream.print(MyString.describe(angularDamping)); - - stream.print(" lin="); - float linearDamping - = softBody.get(Cluster.LinearDamping, clusterIndex); - stream.print(MyString.describe(linearDamping)); - - stream.print(" node="); - float nodeDamping = softBody.get(Cluster.NodeDamping, clusterIndex); - stream.print(MyString.describe(nodeDamping)); - - stream.print("] match="); - float matching = softBody.get(Cluster.Matching, clusterIndex); - stream.print(MyString.describe(matching)); - - stream.print(" scif="); - float selfImpulse = softBody.get(Cluster.SelfImpulse, clusterIndex); - stream.print(MyString.describe(selfImpulse)); - - stream.print(" maxSci="); - float maxSelfImpulse - = softBody.get(Cluster.MaxSelfImpulse, clusterIndex); - stream.print(MyString.describe(maxSelfImpulse)); - - int numNodes = softBody.countNodesInCluster(clusterIndex); - stream.printf(" %d node%s", numNodes, (numNodes == 1) ? "" : "s"); - - if (dumpMotors) { - dumpNodesInCluster(softBody, clusterIndex); - } - } - addLine(indent); - } - - /** - * Dump all ignored objects in the specified collision object. - * - * @param pco the body to dump (not null, unaffected) - * @param indent (not null) - */ - private void dumpIgnores(PhysicsCollisionObject pco, String indent) { - stream.print(':'); - PhysicsCollisionObject[] ignoreList = pco.listIgnoredPcos(); - String moreIndent = indent + indentIncrement(); - for (PhysicsCollisionObject otherPco : ignoreList) { - addLine(moreIndent); - PhysicsDescriber describer = getDescriber(); - String desc = describer.describePco(otherPco, dumpNativeIDs); - stream.print(desc); - } - addLine(indent); - } - - /** - * Dump the specified joints in a PhysicsSpace context. - * - * @param joints (not null, unaffected) - * @param indent (not null, may be empty) - * @param filter determines which physics objects are dumped, or null to - * dump all (unaffected) - */ - private void dumpJoints(Collection joints, - String indent, BulletDebugAppState.DebugAppStateFilter filter) { - for (PhysicsJoint joint : joints) { - if (filter == null || filter.displayObject(joint)) { - dump(joint, indent); - } - } - } - - /** - * Dump all joints in the specified body. - * - * @param body the body to dump (not null, unaffected) - * @param indent (not null) - */ - private void dumpJoints(PhysicsBody body, String indent) { - stream.print(':'); - PhysicsJoint[] joints = body.listJoints(); - PhysicsDescriber describer = getDescriber(); - String moreIndent = indent + indentIncrement(); - for (PhysicsJoint joint : joints) { - String desc - = describer.describeJointInBody(joint, body, dumpNativeIDs); - stream.printf("%n%s%s", moreIndent, desc); - } - } - - /** - * Dump the specified MultiBodyLink. - * - * @param link the link to dump (not null, unaffected) - * @param indent (not null) - * @param filter for colliders (may be null, unaffected) - */ - private void dumpLink(MultiBodyLink link, String indent, - BulletDebugAppState.DebugAppStateFilter filter) { - addLine(indent); - int index = link.index(); - MultiBodyJointType jointType = link.jointType(); - stream.printf("Link[%d] %s->", index, jointType); - - MultiBodyLink parent = link.getParentLink(); - if (parent == null) { - stream.print("base"); - } else { - int parentIndex = parent.index(); - stream.print(parentIndex); - } - - long objectId = link.nativeId(); - addNativeId(objectId); - - MultiBodyCollider collider = link.getCollider(); - if (collider != null) { - if (filter == null || filter.displayObject(collider)) { - dump(collider, indent + indentIncrement()); - } - } - } - - /** - * Dump the specified MultiBody. - * - * @param multibody the multibody to dump (not null, unaffected) - * @param indent (not null) - * @param filter for colliders (may be null, unaffected) - */ - private void dumpMultiBody(MultiBody multibody, String indent, - BulletDebugAppState.DebugAppStateFilter filter) { - addLine(indent); - stream.print("MultiBody"); - PhysicsDescriber describer = getDescriber(); - String desc = describer.describeGroups(multibody); - stream.print(desc); - - if (multibody.hasFixedBase()) { - stream.print("/fixed"); - } - if (!multibody.isUsingGyroTerm()) { - stream.print("/NOgyro"); - } - if (!multibody.canSleep()) { - stream.print("/NOsleep"); - } - if (multibody.isUsingRK4()) { - stream.print("/RK4"); - } - - int numColliders = multibody.listColliders().size(); - int numLinks = multibody.countConfiguredLinks(); - stream.printf(" with %d collider%s, %d link%s", - numColliders, (numColliders == 1) ? "" : "s", - numLinks, (numLinks == 1) ? "" : "s"); - - long objectId = multibody.nativeId(); - addNativeId(objectId); - - addLine(indent); - float angularDamping = multibody.angularDamping(); - float linearDamping = multibody.linearDamping(); - stream.print(" damp[l="); - stream.print(MyString.describe(linearDamping)); - stream.print(" a="); - stream.print(MyString.describe(angularDamping)); - stream.print(']'); - - float maxImp = multibody.maxAppliedImpulse(); - float maxV = multibody.maxCoordinateVelocity(); - stream.print(" max[imp="); - stream.print(MyString.describe(maxImp)); - stream.print(" v="); - stream.print(MyString.describe(maxV)); - stream.print(']'); - - String moreIndent = indent + indentIncrement(); - MultiBodyCollider collider = multibody.getBaseCollider(); - if (collider != null) { - if (filter == null || filter.displayObject(collider)) { - dump(collider, moreIndent); - } - } - for (int linkIndex = 0; linkIndex < numLinks; ++linkIndex) { - MultiBodyLink link = multibody.getLink(linkIndex); - dumpLink(link, moreIndent, filter); - } - } - - /** - * Dump all nodes in the specified soft body. - * - * @param softBody the soft body to dump (not null, unaffected) - * @param indent (not null) - */ - private void dumpNodes(PhysicsSoftBody softBody, String indent) { - stream.print(':'); - - FloatBuffer locations = softBody.copyLocations(null); - FloatBuffer masses = softBody.copyMasses(null); - FloatBuffer velocities = softBody.copyVelocities(null); - IntBuffer linkIndices = softBody.copyLinks(null); - int numNodes = softBody.countNodes(); - int numLinks = softBody.countLinks(); - for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { - int degree = MyBuffer.frequency( - linkIndices, 0, 2 * numLinks, nodeIndex); - float nodeMass = masses.get(nodeIndex); - String locString = describeVector(locations, nodeIndex); - String vString = describeVector(velocities, nodeIndex); - stream.printf("%n%s [%d] deg=%d mass=%s loc[%s] v[%s]", - indent, nodeIndex, degree, MyString.describe(nodeMass), - locString, vString); - } - } - - /** - * Dump the indices of all nodes in the specified cluster. - * - * @param softBody the soft body to dump (not null, unaffected) - * @param clusterIndex which cluster (≥0, <numClusters) - */ - private void dumpNodesInCluster( - PhysicsSoftBody softBody, int clusterIndex) { - IntBuffer nodeIndices = softBody.listNodesInCluster(clusterIndex, null); - int numIndices = nodeIndices.capacity(); - int numNodesInBody = softBody.countNodes(); - if (numIndices == numNodesInBody) { - stream.print("(all)"); - return; - } - - // convert the IntBuffer to a BitSet - BitSet bitSet = new BitSet(numNodesInBody); - for (int i = 0; i < numIndices; ++i) { - int nodeIndex = nodeIndices.get(i); - bitSet.set(nodeIndex); - } - - stream.print('('); - boolean addSeparators = false; - for (int nodeIndex = 0; nodeIndex < numNodesInBody; ++nodeIndex) { - if (bitSet.get(nodeIndex)) { - if (addSeparators) { - stream.print(','); - } else { - addSeparators = true; - } - int runLength = bitSet.nextClearBit(nodeIndex) - nodeIndex; - if (runLength < 3) { - stream.printf("%d", nodeIndex); - } else { - int endIndex = nodeIndex + runLength - 1; - stream.printf("%d-%d", nodeIndex, endIndex); - nodeIndex = endIndex; - } - } - } - stream.print(')'); - } - - /** - * Dump wheels in the specified vehicle. - * - * @param vehicle the vehicle to dump (not null, unaffected) - * @param indent (not null) - * @param numWheels the number of wheels to dump (>0) - */ - private void dumpWheels(PhysicsVehicle vehicle, String indent, - int numWheels) { - stream.print(':'); - PhysicsDescriber describer = getDescriber(); - String moreIndent = indent + indentIncrement(); - for (int wheelIndex = 0; wheelIndex < numWheels; ++wheelIndex) { - stream.printf("%n%s[%d] ", moreIndent, wheelIndex); - VehicleWheel wheel = vehicle.getWheel(wheelIndex); - String desc = describer.describe(wheel); - stream.print(desc); - - stream.printf("%n%s ", moreIndent); - desc = describer.describe2(wheel); - stream.print(desc); - - stream.print(" raycast="); - float raycast = vehicle.castRay(wheelIndex); - stream.print(MyString.describe(raycast)); - - if (raycast >= 0f) { - stream.print(" skid="); - float skid = wheel.getSkidInfo(); - stream.print(MyString.describe(skid)); - } - } - addLine(indent); - } -} +/* + Copyright (c) 2013-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.app.state.AppState; +import com.jme3.bounding.BoundingBox; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.DeformableSpace; +import com.jme3.bullet.MultiBody; +import com.jme3.bullet.MultiBodyJointType; +import com.jme3.bullet.MultiBodyLink; +import com.jme3.bullet.MultiBodySpace; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RayTestFlag; +import com.jme3.bullet.SoftBodyWorldInfo; +import com.jme3.bullet.SolverInfo; +import com.jme3.bullet.SolverMode; +import com.jme3.bullet.SolverType; +import com.jme3.bullet.collision.Activation; +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.GImpactCollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.MeshCollisionShape; +import com.jme3.bullet.collision.shapes.SimplexCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.joints.SixDofJoint; +import com.jme3.bullet.joints.motors.RotationalLimitMotor; +import com.jme3.bullet.joints.motors.TranslationalLimitMotor; +import com.jme3.bullet.objects.MultiBodyCollider; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsGhostObject; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.objects.VehicleWheel; +import com.jme3.bullet.objects.infos.Cluster; +import com.jme3.bullet.objects.infos.RigidBodyMotionState; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import java.io.PrintStream; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.debug.Describer; +import jme3utilities.debug.Dumper; +import jme3utilities.math.MyBuffer; +import jme3utilities.math.MyQuaternion; +import jme3utilities.math.MyVector3f; + +/** + * Dump Minie data structures for debugging purposes. + *

+ * The level of detail can be configured dynamically. + * + * @author Stephen Gold sgold@sonic.net + */ +public class PhysicsDumper extends Dumper { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(PhysicsDumper.class.getName()); + /** + * local copy of {@link com.jme3.math.Vector3f#UNIT_XYZ} + */ + final private static Vector3f scaleIdentity = new Vector3f(1f, 1f, 1f); + // ************************************************************************* + // fields + + /** + * enable dumping of children in compound collision shapes + */ + private boolean dumpChildShapes = false; + /** + * enable dumping of clusters in soft bodies + */ + private boolean dumpClustersInSofts = false; + /** + * enable dumping of ignored objects in collision objects + */ + private boolean dumpIgnores = false; + /** + * enable dumping of physics joints in bodies + */ + private boolean dumpJointsInBodies = false; + /** + * enable dumping of joints in physics spaces + */ + private boolean dumpJointsInSpaces = false; + /** + * enable dumping of motors in physics joints + */ + private boolean dumpMotors = false; + /** + * enable dumping native IDs of physics objects + */ + private boolean dumpNativeIDs = false; + /** + * enable dumping of soft-body nodes in clusters + */ + private boolean dumpNodesInClusters = false; + /** + * enable dumping of nodes in soft bodies + */ + private boolean dumpNodesInSofts = false; + /** + * enable dumping of collision objects in physics spaces + */ + private boolean dumpPcos = true; + // ************************************************************************* + // constructors + + /** + * Instantiate a PhysicsDumper that uses {@code System.out} for output. + */ + public PhysicsDumper() { + super(); + PhysicsDescriber newDescriber = new PhysicsDescriber(); + setDescriber(newDescriber); + } + + /** + * Instantiate a PhysicsDumper that uses the specified output stream. + * + * @param printStream output stream (not null) + */ + public PhysicsDumper(PrintStream printStream) { + super(printStream); + PhysicsDescriber newDescriber = new PhysicsDescriber(); + setDescriber(newDescriber); + } + // ************************************************************************* + // new methods exposed + + /** + * Dump the specified BulletAppState. + * + * @param appState the app state to dump (not null, unaffected) + */ + public void dump(BulletAppState appState) { + Validate.nonNull(appState, "app state"); + dumpBas(appState, ""); + } + + /** + * Dump the specified CollisionShape. + * + * @param shape the shape to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(CollisionShape shape, String indent) { + Validate.nonNull(shape, "shape"); + Validate.nonNull(indent, "indent"); + + addLine(indent); + + PhysicsDescriber describer = getDescriber(); + String desc = describer.describe(shape); + stream.print(desc); + + Vector3f scale = shape.getScale(null); + desc = describer.describeScale(scale); + addDescription(desc); + + long objectId = shape.nativeId(); + addNativeId(objectId); + + if (dumpChildShapes && shape instanceof CompoundCollisionShape) { + String moreIndent = indent + indentIncrement(); + dumpChildren((CompoundCollisionShape) shape, moreIndent); + } + } + + /** + * Dump the specified MultiBodyCollider. + * + * @param collider the collider to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(MultiBodyCollider collider, String indent) { + Validate.nonNull(collider, "collider"); + Validate.nonNull(indent, "indent"); + + addLine(indent); + stream.print("Collider"); + + PhysicsDescriber describer = getDescriber(); + String desc = describer.describeApplicationData(collider); + stream.print(desc); + desc = describer.describeUser(collider); + stream.print(desc); + + if (!collider.isActive()) { + stream.print("/inactive"); + } + if (!collider.isContactResponse()) { + stream.print("/NOresponse"); + } + if (!collider.isInWorld()) { + stream.print("/NOspace"); + } + + float mass = collider.mass(); + String massText = MyString.describe(mass); + stream.printf(" mass=%s", massText); + + Vector3f loc = collider.getPhysicsLocation(null); + stream.printf(" loc[%s]", MyVector3f.describe(loc)); + + desc = describer.describeGroups(collider); + stream.print(desc); + + long objectId = collider.nativeId(); + addNativeId(objectId); + /* + * The 2nd line has the shape and scale. + * There may be additional lines for child shapes. + */ + CollisionShape shape = collider.getCollisionShape(); + dump(shape, indent + " "); + // TODO ignored objects + } + + /** + * Dump the specified PhysicsCharacter. + * + * @param character the character to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(PhysicsCharacter character, String indent) { + Validate.nonNull(character, "character"); + Validate.nonNull(indent, "indent"); + + stream.printf("%n%sCharacter", indent); + + PhysicsDescriber describer = getDescriber(); + String desc = describer.describeApplicationData(character); + stream.print(desc); + desc = describer.describeUser(character); + stream.print(desc); + + Vector3f location = character.getPhysicsLocation(null); + String locString = MyVector3f.describe(location); + stream.printf(" loc[%s]", locString); + + Vector3f walk = character.getWalkDirection(null); + stream.printf(" walk[%s]", MyVector3f.describeDirection(walk)); + + Vector3f lin = character.getLinearVelocity(null); + stream.printf(" v[%s]", MyVector3f.describe(lin)); + + Vector3f ang = character.getAngularVelocity(null); + stream.printf(" angV[%s]", MyVector3f.describe(ang)); + + long objectId = character.nativeId(); + addNativeId(objectId); + + // The 2nd line has the character's configuration. + addLine(indent); + Vector3f grav = character.getGravity(null); + stream.printf(" grav[%s]", MyVector3f.describe(grav)); + + Vector3f up = character.getUpDirection(null); + stream.printf(" up[%s]", MyVector3f.describeDirection(up)); + + stream.print(" jumpSp="); + float jump = character.getJumpSpeed(); + stream.print(MyString.describe(jump)); + + float angularDamping = character.getAngularDamping(); + float linearDamping = character.getLinearDamping(); + stream.print("] damp[l="); + stream.print(MyString.describe(linearDamping)); + stream.print(" a="); + stream.print(MyString.describe(angularDamping)); + + stream.print("] max[fallSp="); + float fall = character.getFallSpeed(); + stream.print(MyString.describe(fall)); + + stream.print(" pen="); + float maxPen = character.getMaxPenetrationDepth(); + stream.print(MyString.describe(maxPen)); + + stream.print(" slope="); + float maxSlope = character.getMaxSlope(); + stream.print(MyString.describe(maxSlope)); + + stream.print(" stepHt="); + float maxStepHt = character.getStepHeight(); + stream.print(MyString.describe(maxStepHt)); + stream.print("] "); + + boolean gsTest = character.isUsingGhostSweepTest(); + if (!gsTest) { + stream.print("NO"); + } + stream.print("gsTest"); + + desc = describer.describeGroups(character); + stream.print(desc); + /* + * The 3rd line has the shape and scale. + * There may be additional lines for child shapes. + */ + CollisionShape shape = character.getCollisionShape(); + dump(shape, indent + " "); + + addLine(indent); + int numIgnores = character.countIgnored(); + stream.printf( + " with %d ignore%s", numIgnores, (numIgnores == 1) ? "" : "s"); + if (dumpIgnores && numIgnores > 0) { + dumpIgnores(character, indent); + } + } + + /** + * Dump the specified PhysicsGhostObject. + * + * @param ghost the ghost object to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(PhysicsGhostObject ghost, String indent) { + Validate.nonNull(ghost, "ghost"); + Validate.nonNull(indent, "indent"); + + stream.printf("%n%sGhost", indent); + + PhysicsDescriber describer = getDescriber(); + String desc = describer.describeApplicationData(ghost); + stream.print(desc); + desc = describer.describeUser(ghost); + stream.print(desc); + + Vector3f location = ghost.getPhysicsLocation(null); + String locString = MyVector3f.describe(location); + stream.printf(" loc[%s]", locString); + + Quaternion orientation = ghost.getPhysicsRotation(null); + if (!MyQuaternion.isRotationIdentity(orientation)) { + String orientText = MyQuaternion.describe(orientation); + stream.printf(" orient[%s]", orientText); + } + + long objectId = ghost.nativeId(); + addNativeId(objectId); + /* + * The 2nd line has the shape and scale. + * There may be additional lines for child shapes. + */ + CollisionShape shape = ghost.getCollisionShape(); + dump(shape, indent + " "); + /* + * The next line has the bounding box, group info, + * and number of ignores. + */ + addLine(indent); + if (shape instanceof CompoundCollisionShape + || shape instanceof GImpactCollisionShape + || shape instanceof HeightfieldCollisionShape + || shape instanceof HullCollisionShape + || shape instanceof MeshCollisionShape + || shape instanceof SimplexCollisionShape) { + BoundingBox aabb = shape.boundingBox(location, orientation, null); + desc = describer.describe(aabb); + stream.printf(" aabb[%s]", desc); + } + + desc = describer.describeGroups(ghost); + stream.print(desc); + + int numIgnores = ghost.countIgnored(); + stream.printf( + " with %d ignore%s", numIgnores, (numIgnores == 1) ? "" : "s"); + if (dumpIgnores && numIgnores > 0) { + dumpIgnores(ghost, indent); + } + } + + /** + * Dump the specified PhysicsJoint in a PhysicsSpace context. + * + * @param joint the joint to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(PhysicsJoint joint, String indent) { + Validate.nonNull(joint, "joint"); + Validate.nonNull(indent, "indent"); + + String moreIndent = indent + indentIncrement(); + addLine(moreIndent); + PhysicsDescriber describer = getDescriber(); + String desc = describer.describeJointInSpace(joint, dumpNativeIDs); + stream.print(desc); + + String mmIndent = moreIndent + indentIncrement(); + if (joint instanceof SixDofJoint) { + SixDofJoint sixDof = (SixDofJoint) joint; + + desc = describer.describeAngular(sixDof); + stream.printf("%n%s %s", moreIndent, desc); + desc = describer.describeLinear(sixDof); + stream.printf("%n%s %s", moreIndent, desc); + + if (dumpMotors) { + for (int axisI = 0; axisI < MyVector3f.numAxes; ++axisI) { + String axisName = MyString.axisName(axisI); + stream.printf("%n%srot%s: ", mmIndent, axisName); + RotationalLimitMotor motor + = sixDof.getRotationalLimitMotor(axisI); + desc = describer.describe(motor); + stream.print(desc); + } + + TranslationalLimitMotor motor + = sixDof.getTranslationalLimitMotor(); + for (int axisI = 0; axisI < MyVector3f.numAxes; ++axisI) { + String axisName = MyString.axisName(axisI); + stream.printf("%n%stra%s: ", mmIndent, axisName); + desc = describer.describe(motor, axisI); + stream.print(desc); + } + } + + } else if (joint instanceof New6Dof) { + New6Dof sixDof = (New6Dof) joint; + + addLine(moreIndent); + Vector3f offset = sixDof.getPivotOffset(null); + stream.printf(" offset[%s]", MyVector3f.describe(offset)); + Vector3f locA = sixDof.calculatedOriginA(null); + stream.printf(" locA[%s]", MyVector3f.describe(locA)); + Vector3f locB = sixDof.calculatedOriginB(null); + stream.printf(" locB[%s]", MyVector3f.describe(locB)); + + addLine(moreIndent); + Vector3f angles = sixDof.getAngles(null); + stream.printf(" angles[%s]", MyVector3f.describe(angles)); + desc = sixDof.getRotationOrder().toString(); + stream.printf(" ro=%s", desc); + Matrix3f basA = sixDof.calculatedBasisA(null); + stream.printf(" basA[%s]", MyString.describeMatrix(basA)); + Matrix3f basB = sixDof.calculatedBasisB(null); + stream.printf(" basB[%s]", MyString.describeMatrix(basB)); + + if (dumpMotors) { + for (int dofIndex = 0; dofIndex < 6; ++dofIndex) { + int axisIndex = dofIndex % MyVector3f.numAxes; + String tr = (dofIndex < 3) ? "T" : "R"; + String axisName = MyString.axisName(axisIndex); + stream.printf("%n%s%s%s:", mmIndent, tr, axisName); + desc = describer.describeDof(sixDof, dofIndex); + stream.print(desc); + } + } + } + } + + /** + * Dump the specified PhysicsRigidBody. + * + * @param body the rigid body to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(PhysicsRigidBody body, String indent) { + Validate.nonNull(body, "body"); + Validate.nonNull(indent, "indent"); + + addLine(indent); + if (body instanceof PhysicsVehicle) { + stream.print("Vehicle "); + } else { + stream.print("Rigid "); + } + + String desc = MyPco.describe(body); + stream.print(desc); + + PhysicsDescriber describer = getDescriber(); + desc = describer.describeApplicationData(body); + stream.print(desc); + desc = describer.describeUser(body); + stream.print(desc); + + RigidBodyMotionState motionState = body.getMotionState(); + Vector3f msLoc = motionState.getLocation(null); + String locString = MyVector3f.describe(msLoc); + stream.printf(" msLoc[%s]", locString); + Vector3f location = body.getPhysicsLocation(null); + if (!location.equals(msLoc)) { + locString = MyVector3f.describe(location); + stream.printf(" loc[%s]", locString); + } + + Quaternion orientation = body.getPhysicsRotation(null); + if (!MyQuaternion.isRotationIdentity(orientation)) { + String orientText = MyQuaternion.describe(orientation); + stream.printf(" orient[%s]", orientText); + } + + long objectId = body.nativeId(); + addNativeId(objectId); + + // 2nd line: activation state and contact parameters + addLine(indent); + addActivationState(body); + addContactParameters(body); + + if (body.isDynamic()) { + // The next 3 lines describes the dynamic properties. + addDynamicProperties(body, indent); + } + /* + * The next line has the shape and scale. + * There may be additional lines for child shapes. + */ + CollisionShape shape = body.getCollisionShape(); + dump(shape, indent + " "); + /* + * The next line has the bounding box, group info, number of wheels, + * and number of ignores/joints. + */ + addLine(indent); + if (shape instanceof CompoundCollisionShape + || shape instanceof GImpactCollisionShape + || shape instanceof HeightfieldCollisionShape + || shape instanceof HullCollisionShape + || shape instanceof MeshCollisionShape + || shape instanceof SimplexCollisionShape) { + BoundingBox aabb = shape.boundingBox(location, orientation, null); + desc = describer.describe(aabb); + stream.printf(" aabb[%s]", desc); + } + + desc = describer.describeGroups(body); + stream.print(desc); + + stream.print(" with"); + if (body instanceof PhysicsVehicle) { + PhysicsVehicle vehicle = (PhysicsVehicle) body; + int numWheels = vehicle.getNumWheels(); + stream.printf( + " %d wheel%s", numWheels, (numWheels == 1) ? "" : "s"); + if (numWheels > 0) { + dumpWheels(vehicle, indent, numWheels); + } else { + stream.print(','); + } + } + + int numIgnores = body.countIgnored(); + stream.printf(" %d ignore%s", numIgnores, (numIgnores == 1) ? "" : "s"); + if (dumpIgnores && numIgnores > 0) { + dumpIgnores(body, indent); + } + + int numJoints = body.countJoints(); + stream.printf( + " and %d joint%s", numJoints, (numJoints == 1) ? "" : "s"); + if (dumpJointsInBodies && numJoints > 0) { + dumpJoints(body, indent); + } + } + + /** + * Dump the specified PhysicsSoftBody. + * + * @param body the soft body to dump (not null, unaffected) + * @param indent (not null) + */ + public void dump(PhysicsSoftBody body, String indent) { + Validate.nonNull(body, "body"); + Validate.nonNull(indent, "indent"); + + stream.printf("%n%sSoft ", indent); + + PhysicsDescriber describer = getDescriber(); + BoundingBox aabb = body.boundingBox(null); + String desc = describer.describe(aabb); + stream.print(desc); + + stream.print(" mass="); + float mass = body.getMass(); + desc = MyString.describe(mass); + stream.print(desc); + + stream.print(" marg="); + float margin = body.margin(); + desc = MyString.describe(margin); + stream.print(desc); + + long objectId = body.nativeId(); + addNativeId(objectId); + + stream.printf("%n%s vol=", indent); + float volume = body.volume(); + desc = MyString.describe(volume); + stream.print(desc); + + stream.print(" wind["); + Vector3f wind = body.windVelocity(null); + desc = MyVector3f.describe(wind); + stream.print(desc); + stream.print(']'); + + desc = describer.describeApplicationData(body); + stream.print(desc); + desc = describer.describeUser(body); + stream.print(desc); + + int numLinks = body.countLinks(); + int numFaces = body.countFaces(); + int numTetras = body.countTetras(); + stream.printf(" with %d link%s, %d face%s, %d tetra%s", + numLinks, (numLinks == 1) ? "" : "s", + numFaces, (numFaces == 1) ? "" : "s", + numTetras, (numTetras == 1) ? "" : "s"); + + Quaternion orientation = body.getPhysicsRotation(null); + if (!MyQuaternion.isRotationIdentity(orientation)) { + desc = MyQuaternion.describe(orientation); + stream.printf(" orient[%s]", desc); + } + + // 3rd & 4th lines describe the config. + SoftBodyConfig config = body.getSoftConfig(); + desc = describer.describe1(config); + stream.printf("%n%s %s", indent, desc); + desc = describer.describe2(config); + stream.printf("%n%s %s", indent, desc); + + // 5th line describes the material. + SoftBodyMaterial material = body.getSoftMaterial(); + desc = describer.describe(material); + stream.printf("%n%s %s", indent, desc); + + // 6th line describes the world info. + SoftBodyWorldInfo info = body.getWorldInfo(); + desc = describer.describe(info); + stream.printf("%n%s %s ", indent, desc); + if (!body.isWorldInfoProtected()) { + stream.print("NOT"); + } + stream.print("protected"); + objectId = info.nativeId(); + addNativeId(objectId); + + // 7th line describes the group info and number of anchors. + desc = describer.describeGroups(body); + stream.printf("%n%s%s", indent, desc); + + // physics joints in the soft body + int numJoints = body.countJoints(); + stream.printf( + " with %d joint%s", numJoints, (numJoints == 1) ? "" : "s"); + if (dumpJointsInBodies && numJoints > 0) { + dumpJoints(body, indent); + addLine(indent); + } else { + stream.print(','); + } + + // clusters in the soft body + int numClusters = body.countClusters(); + stream.printf( + " %d cluster%s", numClusters, (numClusters == 1) ? "" : "s"); + if (dumpClustersInSofts && numClusters > 0) { + dumpClusters(body, indent); + } else { + stream.print(','); + } + + // nodes in the soft body + int numNodes = body.countNodes(); + stream.printf(" %d node%s", numNodes, (numNodes == 1) ? "" : "s"); + int numPinned = body.countPinnedNodes(); + if (numPinned > 0) { + stream.printf(" (%d pinned)", numPinned); + } + if (dumpNodesInSofts && numNodes > 0) { + dumpNodes(body, indent); + } + // TODO ignored objects + } + + /** + * Dump the specified PhysicsSpace. + * + * @param space the PhysicsSpace to dump (not null, unaffected) + */ + public void dump(PhysicsSpace space) { + dump(space, "", null); + } + + /** + * Dump the specified PhysicsSpace with the specified filter. TODO dump a + * CollisionSpace + * + * @param space the PhysicsSpace to dump (not null, unaffected) + * @param indent (not null, may be empty) + * @param filter determines which physics objects are dumped, or null to + * dump all (unaffected) + */ + public void dump(PhysicsSpace space, String indent, + BulletDebugAppState.DebugAppStateFilter filter) { + Validate.nonNull(indent, "indent"); + + String type = space.getClass().getSimpleName(); + Collection characters = space.getCharacterList(); + int numCharacters = characters.size(); + Collection ghosts = space.getGhostObjectList(); + int numGhosts = ghosts.size(); + stream.printf("%n%s%s with %d char%s, %d ghost%s, ", + indent, type, numCharacters, (numCharacters == 1) ? "" : "s", + numGhosts, (numGhosts == 1) ? "" : "s"); + + Collection joints = space.getJointList(); + int numJoints = joints.size(); + stream.printf("%d joint%s, ", numJoints, (numJoints == 1) ? "" : "s"); + + Collection multibodies = new ArrayList<>(0); + if (space instanceof MultiBodySpace) { + multibodies = ((MultiBodySpace) space).getMultiBodyList(); + int numMultis = multibodies.size(); + stream.printf( + "%d multi%s, ", numMultis, (numMultis == 1) ? "" : "s"); + } + + Collection rigidBodies = space.getRigidBodyList(); + int numRigids = rigidBodies.size(); + stream.printf("%d rigid%s, ", numRigids, (numRigids == 1) ? "" : "s"); + + Collection softBodies = new ArrayList<>(0); + if (space instanceof PhysicsSoftSpace) { + softBodies = ((PhysicsSoftSpace) space).getSoftBodyList(); + int numSofts = softBodies.size(); + stream.printf("%d soft%s, ", numSofts, (numSofts == 1) ? "" : "s"); + } else if (space instanceof DeformableSpace) { + softBodies = ((DeformableSpace) space).getSoftBodyList(); + int numSofts = softBodies.size(); + stream.printf("%d soft%s, ", numSofts, (numSofts == 1) ? "" : "s"); + } + + int numVehicles = space.getVehicleList().size(); + stream.printf("%d vehicle%s", numVehicles, + (numVehicles == 1) ? "" : "s"); + + long spaceId = space.nativeId(); + addNativeId(spaceId); + + // 2nd line + addLine(indent); + PhysicsSpace.BroadphaseType bphase = space.getBroadphaseType(); + stream.printf(" bphase=%s", bphase); + + Vector3f grav = space.getGravity(null); + stream.printf(" grav[%s] timeStep[", MyVector3f.describe(grav)); + int maxSS = space.maxSubSteps(); + if (maxSS == 0) { + float maxTimeStep = space.maxTimeStep(); + String mtsDesc = MyString.describe(maxTimeStep); + stream.printf("VAR max=%s", mtsDesc); + } else { + float accuracy = space.getAccuracy(); + String accuDesc = MyString.describe(accuracy); + stream.printf("%s maxSS=%d", accuDesc, maxSS); + } + + int cCount = space.countCollisionListeners(); + int cgCount = space.countCollisionGroupListeners(); + int tCount = space.countTickListeners(); + stream.printf("] listeners[c=%d cg=%d t=%d]", cCount, cgCount, tCount); + + // 3rd line: solver type and info + addLine(indent); + SolverType solverType = space.getSolverType(); + SolverInfo solverInfo = space.getSolverInfo(); + int iters = solverInfo.numIterations(); + float cfm = solverInfo.globalCfm(); + stream.printf(" solver[%s iters=%d cfm=%s", + solverType, iters, MyString.describe(cfm)); + int batch = solverInfo.minBatch(); + stream.printf(" batch=%d splitImp[th=", batch); + boolean enabledGlobally = solverInfo.isSplitImpulseEnabled(); + if (enabledGlobally) { + stream.print("global"); + } else { + float th = solverInfo.splitImpulseThreshold(); + stream.print(MyString.describe(th)); + } + float erp = solverInfo.splitImpulseErp(); + stream.printf(" erp=%s]", MyString.describe(erp)); + int mode = solverInfo.mode(); + stream.printf(" mode=%s]", SolverMode.describe(mode)); + + // 4th line: use flags, raytest flags, and world extent + addLine(indent); + if (space.isCcdWithStaticOnly()) { + stream.print(" CCDwso"); + } + if (space.isUsingDeterministicDispatch()) { + stream.print(" DeterministicDispatch"); + } + if (space.isUsingScr()) { + stream.print(" SCR"); + } + int rayTestFlags = space.getRayTestFlags(); + String rayTest = RayTestFlag.describe(rayTestFlags); + stream.printf(" rayTest=%s", rayTest); + + if (bphase == PhysicsSpace.BroadphaseType.AXIS_SWEEP_3 + || bphase == PhysicsSpace.BroadphaseType.AXIS_SWEEP_3_32) { + Vector3f worldMin = space.getWorldMin(null); + String minDesc = MyVector3f.describe(worldMin); + Vector3f worldMax = space.getWorldMax(null); + String maxDesc = MyVector3f.describe(worldMax); + stream.printf(" worldMin[%s] worldMax[%s]", minDesc, maxDesc); + } + + // For soft spaces, 5th line has the world info. + PhysicsDescriber describer = getDescriber(); + if (space instanceof PhysicsSoftSpace) { + SoftBodyWorldInfo info = ((PhysicsSoftSpace) space).getWorldInfo(); + String infoDesc = describer.describe(info); + stream.printf("%n%s %s", indent, infoDesc); + long objectId = info.nativeId(); + addNativeId(objectId); + } + + if (dumpPcos) { + String moreIndent = indent + indentIncrement(); + for (PhysicsCharacter character : characters) { + if (filter == null || filter.displayObject(character)) { + dump(character, moreIndent); + } + } + for (PhysicsGhostObject ghost : ghosts) { + if (filter == null || filter.displayObject(ghost)) { + dump(ghost, moreIndent); + } + } + for (MultiBody multibody : multibodies) { + dumpMultiBody(multibody, moreIndent, filter); + } + for (PhysicsRigidBody rigid : rigidBodies) { + if (filter == null || filter.displayObject(rigid)) { + dump(rigid, moreIndent); + } + } + for (PhysicsSoftBody soft : softBodies) { + if (filter == null || filter.displayObject(soft)) { + dump(soft, moreIndent); + } + } + } + + if (dumpJointsInSpaces) { + dumpJoints(joints, indent, filter); + } + + stream.println(); + } + + /** + * Dump the specified BulletAppState. + * + * @param appState the app state to dump (not null, unaffected) + * @param indent (not null) + */ + public void dumpBas(BulletAppState appState, String indent) { + Validate.nonNull(indent, "indent"); + + String className = appState.getClass().getSimpleName(); + stream.print(className); + + if (appState.isEnabled()) { + stream.print(" enabled "); + + if (!appState.isDebugEnabled()) { + stream.print("NO"); + } + stream.print("debug "); + + float speed = appState.getSpeed(); + String speedString = MyString.describe(speed); + stream.printf("speed=%s", speedString); + + PhysicsSpace.BroadphaseType broadphaseType + = appState.getBroadphaseType(); + stream.printf(" bphase=%s", broadphaseType); + + PhysicsSpace space = appState.getPhysicsSpace(); + String moreIndent = indent + indentIncrement(); + dump(space, moreIndent, null); + } else { + stream.println(" disabled"); + } + } + + /** + * Test whether the specified dump flag is set. + * + * @param dumpFlag which flag to test (not null) + * @return true if output is enabled, otherwise false + */ + public boolean isEnabled(DumpFlags dumpFlag) { + boolean result; + + switch (dumpFlag) { + case BoundsInSpatials: + result = isDumpBounds(); + break; + + case Buckets: + result = isDumpBucket(); + break; + + case ChildShapes: + result = dumpChildShapes; + break; + + case ClustersInSofts: + result = dumpClustersInSofts; + break; + + case CullHints: + result = isDumpCull(); + break; + + case Ignores: + result = dumpIgnores; + break; + + case JointsInBodies: + result = dumpJointsInBodies; + break; + + case JointsInSpaces: + result = dumpJointsInSpaces; + break; + + case MatParams: + result = isDumpMatParam(); + break; + + case Motors: + result = dumpMotors; + break; + + case NativeIDs: + result = dumpNativeIDs; + break; + + case NodesInClusters: + result = dumpNodesInClusters; + break; + + case NodesInSofts: + result = dumpNodesInSofts; + break; + + case Overrides: + result = isDumpOverride(); + break; + + case Pcos: + result = dumpPcos; + break; + + case ShadowModes: + result = isDumpShadow(); + break; + + case Transforms: + result = isDumpTransform(); + break; + + case UserData: + result = isDumpUser(); + break; + + case VertexData: + result = isDumpVertex(); + break; + + default: + throw new IllegalArgumentException("dumpFlag = " + dumpFlag); + } + + return result; + } + + /** + * Configure the specified dump flag. + * + * @param dumpFlag which flag to set (not null) + * @param newValue true to enable output, false to disable it + * @return this instance for chaining + */ + public PhysicsDumper setEnabled(DumpFlags dumpFlag, boolean newValue) { + switch (dumpFlag) { + case BoundsInSpatials: + setDumpBounds(newValue); + break; + + case Buckets: + setDumpBucket(newValue); + break; + + case ChildShapes: + this.dumpChildShapes = newValue; + break; + + case ClustersInSofts: + this.dumpClustersInSofts = newValue; + break; + + case CullHints: + setDumpCull(newValue); + break; + + case Ignores: + this.dumpIgnores = newValue; + break; + + case JointsInBodies: + this.dumpJointsInBodies = newValue; + break; + + case JointsInSpaces: + this.dumpJointsInSpaces = newValue; + break; + + case MatParams: + setDumpMatParam(newValue); + break; + + case Motors: + this.dumpMotors = newValue; + break; + + case NativeIDs: + this.dumpNativeIDs = newValue; + break; + + case NodesInClusters: + this.dumpNodesInClusters = newValue; + break; + + case NodesInSofts: + this.dumpNodesInSofts = newValue; + break; + + case Overrides: + setDumpOverride(newValue); + break; + + case Pcos: + this.dumpPcos = newValue; + break; + + case ShadowModes: + setDumpShadow(newValue); + break; + + case Transforms: + setDumpTransform(newValue); + break; + + case UserData: + setDumpUser(newValue); + break; + + case VertexData: + setDumpVertex(newValue); + break; + + default: + throw new IllegalArgumentException("dumpFlag = " + dumpFlag); + } + + return this; + } + // ************************************************************************* + // Dumper methods + + /** + * Create a deep copy of this dumper. + * + * @return a new instance, equivalent to this one, with its own Describer + * @throws CloneNotSupportedException if the superclass isn't cloneable + */ + @Override + public PhysicsDumper clone() throws CloneNotSupportedException { + PhysicsDumper clone = (PhysicsDumper) super.clone(); + return clone; + } + + /** + * Dump the specified AppState. + * + * @param appState the AppState to dump (not null, unaffected) + * @param indent (not null) + */ + @Override + public void dump(AppState appState, String indent) { + Validate.nonNull(appState, "app state"); + Validate.nonNull(indent, "indent"); + + if (appState instanceof BulletAppState) { + dumpBas((BulletAppState) appState, indent); + } else { + super.dump(appState, indent); + } + } + + /** + * Access the Describer used by this Dumper. + * + * @return the pre-existing instance (not null) + */ + @Override + public PhysicsDescriber getDescriber() { + Describer describer = super.getDescriber(); + PhysicsDescriber result = (PhysicsDescriber) describer; + + return result; + } + // ************************************************************************* + // private methods + + /** + * Print the activation state of the specified rigid body, unless it happens + * to be in the expected state. + * + * @param body (not null, unaffected) + */ + private void addActivationState(PhysicsRigidBody body) { + int expectedState; + if (body.isKinematic() || body instanceof PhysicsVehicle) { + expectedState = Activation.exempt; + } else if (body.isActive()) { + expectedState = Activation.active; + } else { + expectedState = Activation.sleeping; + } + + int activationState = body.getActivationState(); + if (activationState != expectedState) { + stream.printf(" act=%d", activationState); + } + } + + /** + * Print the contact parameters of the specified rigid body. + * + * @param body (not null, unaffected) + */ + private void addContactParameters(PhysicsRigidBody body) { + float fric = body.getFriction(); + stream.print(" contact[fric="); + stream.print(MyString.describe(fric)); + + float rest = body.getRestitution(); + stream.print(" rest="); + stream.print(MyString.describe(rest)); + + float damp = body.getContactDamping(); + stream.print(" damp="); + stream.print(MyString.describe(damp)); + + float pth = body.getContactProcessingThreshold(); + stream.print(" pth="); + stream.print(MyString.describe(pth)); + + float stiff = body.getContactStiffness(); + stream.print(" stiff="); + stream.print(MyString.describe(stiff)); + stream.print(']'); + } + + /** + * Print dynamic properties of the specified rigid body. + * + * @param rigidBody (not null, unaffected) + * @param indent (not null) + */ + private void addDynamicProperties(PhysicsRigidBody rigidBody, + String indent) { + // first line: gravity, CCD, damping, and sleep/activation + addLine(indent); + + Vector3f gravity = rigidBody.getGravity(null); + String graString = MyVector3f.describe(gravity); + stream.printf(" grav[%s] ", graString); + + if (!rigidBody.isGravityProtected()) { + stream.print("NOT"); + } + stream.print("protected ccd[mth="); + float ccdMt = rigidBody.getCcdMotionThreshold(); + stream.print(MyString.describe(ccdMt)); + if (ccdMt > 0f) { + stream.print(" r="); + float ccdR = rigidBody.getCcdSweptSphereRadius(); + stream.print(MyString.describe(ccdR)); + } + + float angularDamping = rigidBody.getAngularDamping(); + float linearDamping = rigidBody.getLinearDamping(); + stream.print("] damp[l="); + stream.print(MyString.describe(linearDamping)); + stream.print(" a="); + stream.print(MyString.describe(angularDamping)); + + float linearThreshold = rigidBody.getLinearSleepingThreshold(); + float angularThreshold = rigidBody.getAngularSleepingThreshold(); + stream.print("] sleep[lth="); + stream.print(MyString.describe(linearThreshold)); + stream.print(" ath="); + stream.print(MyString.describe(angularThreshold)); + if (rigidBody.isActive()) { + float deactivationTime = rigidBody.getDeactivationTime(); + stream.print(" time="); + stream.print(MyString.describe(deactivationTime)); + } + stream.print(']'); + + // 2nd line: linear velocity, applied force, linear factor + addLine(indent); + + Vector3f v = rigidBody.getLinearVelocity(null); + stream.printf(" v[%s]", MyVector3f.describe(v)); + Vector3f force = rigidBody.totalAppliedForce(null); + stream.printf(" force[%s]", MyVector3f.describe(force)); + Vector3f lFact = rigidBody.getLinearFactor(null); + stream.printf(" lFact[%s]", MyVector3f.describe(lFact)); + + // 3rd line: inertia, angular velocity, applied torque, angular factor + addLine(indent); + + stream.print(" inert["); + Vector3f iiLocal = rigidBody.getInverseInertiaLocal(null); + Vector3f inert = scaleIdentity.divide(iiLocal); + stream.print(MyVector3f.describe(inert)); + stream.print(']'); + + Vector3f angularVelocity = rigidBody.getAngularVelocity(null); + stream.printf(" w[%s]", MyVector3f.describe(angularVelocity)); + Vector3f torq = rigidBody.totalAppliedTorque(null); + stream.printf(" torq[%s]", MyVector3f.describe(torq)); + Vector3f aFact = rigidBody.getAngularFactor(null); + stream.printf(" aFact[%s]", MyVector3f.describe(aFact)); + } + + /** + * Add a native ID, if the flag is set. + * + * @param id the unique identifier (not zero) + */ + private void addNativeId(long id) { + if (dumpNativeIDs) { + stream.print(" #"); + String hex = Long.toHexString(id); + stream.print(hex); + } + } + + /** + * Generate a textual description of the indexed vector in the specified + * FloatBuffer. + * + * @param buffer the buffer to read (not null, unaffected) + * @param vectorIndex the index of the vector in the buffer (≥0) + * @return descriptive text (not null, not empty) + */ + private static String describeVector(FloatBuffer buffer, int vectorIndex) { + Vector3f vector = new Vector3f(); + MyBuffer.get(buffer, MyVector3f.numAxes * vectorIndex, vector); + String locString = MyVector3f.describe(vector); + + return locString; + } + + /** + * Dump all children of the specified CompoundCollisionShape. + * + * @param parent the shape to dump (not null, unaffected) + * @param indent (not null) + */ + private void dumpChildren(CompoundCollisionShape parent, String indent) { + PhysicsDescriber describer = getDescriber(); + ChildCollisionShape[] children = parent.listChildren(); + for (ChildCollisionShape child : children) { + addLine(indent); + CollisionShape baseShape = child.getShape(); + String desc = describer.describe(baseShape); + stream.print(desc); + + Vector3f offset = child.copyOffset(null); + if (!MyVector3f.isZero(offset)) { + stream.print(" offset["); + desc = MyVector3f.describe(offset); + stream.print(desc); + stream.print(']'); + } + + Quaternion rot = child.copyRotation(null); + if (!MyQuaternion.isRotationIdentity(rot)) { + stream.print(" rot["); + desc = MyQuaternion.describe(rot); + stream.print(desc); + stream.print(']'); + } + + Vector3f scale = baseShape.getScale(null); + desc = describer.describeScale(scale); + addDescription(desc); + + long objectId = baseShape.nativeId(); + addNativeId(objectId); + } + } + + /** + * Dump all clusters in the specified soft body. + * + * @param softBody the body to dump (not null, unaffected) + * @param indent (not null) + */ + private void dumpClusters(PhysicsSoftBody softBody, String indent) { + stream.print(':'); + FloatBuffer coms = softBody.copyClusterCenters(null); + FloatBuffer masses = softBody.copyClusterMasses(null); + int numClusters = softBody.countClusters(); + for (int clusterIndex = 0; clusterIndex < numClusters; ++clusterIndex) { + stream.printf("%n%s [%d] com[", indent, clusterIndex); + String desc = describeVector(coms, clusterIndex); + stream.print(desc); + + stream.print("] mass="); + float mass = masses.get(clusterIndex); + stream.print(MyString.describe(mass)); + + stream.print(" damp[ang="); + float angularDamping + = softBody.get(Cluster.AngularDamping, clusterIndex); + stream.print(MyString.describe(angularDamping)); + + stream.print(" lin="); + float linearDamping + = softBody.get(Cluster.LinearDamping, clusterIndex); + stream.print(MyString.describe(linearDamping)); + + stream.print(" node="); + float nodeDamping = softBody.get(Cluster.NodeDamping, clusterIndex); + stream.print(MyString.describe(nodeDamping)); + + stream.print("] match="); + float matching = softBody.get(Cluster.Matching, clusterIndex); + stream.print(MyString.describe(matching)); + + stream.print(" scif="); + float selfImpulse = softBody.get(Cluster.SelfImpulse, clusterIndex); + stream.print(MyString.describe(selfImpulse)); + + stream.print(" maxSci="); + float maxSelfImpulse + = softBody.get(Cluster.MaxSelfImpulse, clusterIndex); + stream.print(MyString.describe(maxSelfImpulse)); + + int numNodes = softBody.countNodesInCluster(clusterIndex); + stream.printf(" %d node%s", numNodes, (numNodes == 1) ? "" : "s"); + + if (dumpMotors) { + dumpNodesInCluster(softBody, clusterIndex); + } + } + addLine(indent); + } + + /** + * Dump all ignored objects in the specified collision object. + * + * @param pco the body to dump (not null, unaffected) + * @param indent (not null) + */ + private void dumpIgnores(PhysicsCollisionObject pco, String indent) { + stream.print(':'); + PhysicsCollisionObject[] ignoreList = pco.listIgnoredPcos(); + String moreIndent = indent + indentIncrement(); + for (PhysicsCollisionObject otherPco : ignoreList) { + addLine(moreIndent); + PhysicsDescriber describer = getDescriber(); + String desc = describer.describePco(otherPco, dumpNativeIDs); + stream.print(desc); + } + addLine(indent); + } + + /** + * Dump the specified joints in a PhysicsSpace context. + * + * @param joints (not null, unaffected) + * @param indent (not null, may be empty) + * @param filter determines which physics objects are dumped, or null to + * dump all (unaffected) + */ + private void dumpJoints(Collection joints, + String indent, BulletDebugAppState.DebugAppStateFilter filter) { + for (PhysicsJoint joint : joints) { + if (filter == null || filter.displayObject(joint)) { + dump(joint, indent); + } + } + } + + /** + * Dump all joints in the specified body. + * + * @param body the body to dump (not null, unaffected) + * @param indent (not null) + */ + private void dumpJoints(PhysicsBody body, String indent) { + stream.print(':'); + PhysicsJoint[] joints = body.listJoints(); + PhysicsDescriber describer = getDescriber(); + String moreIndent = indent + indentIncrement(); + for (PhysicsJoint joint : joints) { + String desc + = describer.describeJointInBody(joint, body, dumpNativeIDs); + stream.printf("%n%s%s", moreIndent, desc); + } + } + + /** + * Dump the specified MultiBodyLink. + * + * @param link the link to dump (not null, unaffected) + * @param indent (not null) + * @param filter for colliders (may be null, unaffected) + */ + private void dumpLink(MultiBodyLink link, String indent, + BulletDebugAppState.DebugAppStateFilter filter) { + addLine(indent); + int index = link.index(); + MultiBodyJointType jointType = link.jointType(); + stream.printf("Link[%d] %s->", index, jointType); + + MultiBodyLink parent = link.getParentLink(); + if (parent == null) { + stream.print("base"); + } else { + int parentIndex = parent.index(); + stream.print(parentIndex); + } + + long objectId = link.nativeId(); + addNativeId(objectId); + + MultiBodyCollider collider = link.getCollider(); + if (collider != null) { + if (filter == null || filter.displayObject(collider)) { + dump(collider, indent + indentIncrement()); + } + } + } + + /** + * Dump the specified MultiBody. + * + * @param multibody the multibody to dump (not null, unaffected) + * @param indent (not null) + * @param filter for colliders (may be null, unaffected) + */ + private void dumpMultiBody(MultiBody multibody, String indent, + BulletDebugAppState.DebugAppStateFilter filter) { + addLine(indent); + stream.print("MultiBody"); + PhysicsDescriber describer = getDescriber(); + String desc = describer.describeGroups(multibody); + stream.print(desc); + + if (multibody.hasFixedBase()) { + stream.print("/fixed"); + } + if (!multibody.isUsingGyroTerm()) { + stream.print("/NOgyro"); + } + if (!multibody.canSleep()) { + stream.print("/NOsleep"); + } + if (multibody.isUsingRK4()) { + stream.print("/RK4"); + } + + int numColliders = multibody.listColliders().size(); + int numLinks = multibody.countConfiguredLinks(); + stream.printf(" with %d collider%s, %d link%s", + numColliders, (numColliders == 1) ? "" : "s", + numLinks, (numLinks == 1) ? "" : "s"); + + long objectId = multibody.nativeId(); + addNativeId(objectId); + + addLine(indent); + float angularDamping = multibody.angularDamping(); + float linearDamping = multibody.linearDamping(); + stream.print(" damp[l="); + stream.print(MyString.describe(linearDamping)); + stream.print(" a="); + stream.print(MyString.describe(angularDamping)); + stream.print(']'); + + float maxImp = multibody.maxAppliedImpulse(); + float maxV = multibody.maxCoordinateVelocity(); + stream.print(" max[imp="); + stream.print(MyString.describe(maxImp)); + stream.print(" v="); + stream.print(MyString.describe(maxV)); + stream.print(']'); + + String moreIndent = indent + indentIncrement(); + MultiBodyCollider collider = multibody.getBaseCollider(); + if (collider != null) { + if (filter == null || filter.displayObject(collider)) { + dump(collider, moreIndent); + } + } + for (int linkIndex = 0; linkIndex < numLinks; ++linkIndex) { + MultiBodyLink link = multibody.getLink(linkIndex); + dumpLink(link, moreIndent, filter); + } + } + + /** + * Dump all nodes in the specified soft body. + * + * @param softBody the soft body to dump (not null, unaffected) + * @param indent (not null) + */ + private void dumpNodes(PhysicsSoftBody softBody, String indent) { + stream.print(':'); + + FloatBuffer locations = softBody.copyLocations(null); + FloatBuffer masses = softBody.copyMasses(null); + FloatBuffer velocities = softBody.copyVelocities(null); + IntBuffer linkIndices = softBody.copyLinks(null); + int numNodes = softBody.countNodes(); + int numLinks = softBody.countLinks(); + for (int nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex) { + int degree = MyBuffer.frequency( + linkIndices, 0, 2 * numLinks, nodeIndex); + float nodeMass = masses.get(nodeIndex); + String locString = describeVector(locations, nodeIndex); + String vString = describeVector(velocities, nodeIndex); + stream.printf("%n%s [%d] deg=%d mass=%s loc[%s] v[%s]", + indent, nodeIndex, degree, MyString.describe(nodeMass), + locString, vString); + } + } + + /** + * Dump the indices of all nodes in the specified cluster. + * + * @param softBody the soft body to dump (not null, unaffected) + * @param clusterIndex which cluster (≥0, <numClusters) + */ + private void dumpNodesInCluster( + PhysicsSoftBody softBody, int clusterIndex) { + IntBuffer nodeIndices = softBody.listNodesInCluster(clusterIndex, null); + int numIndices = nodeIndices.capacity(); + int numNodesInBody = softBody.countNodes(); + if (numIndices == numNodesInBody) { + stream.print("(all)"); + return; + } + + // convert the IntBuffer to a BitSet + BitSet bitSet = new BitSet(numNodesInBody); + for (int i = 0; i < numIndices; ++i) { + int nodeIndex = nodeIndices.get(i); + bitSet.set(nodeIndex); + } + + stream.print('('); + boolean addSeparators = false; + for (int nodeIndex = 0; nodeIndex < numNodesInBody; ++nodeIndex) { + if (bitSet.get(nodeIndex)) { + if (addSeparators) { + stream.print(','); + } else { + addSeparators = true; + } + int runLength = bitSet.nextClearBit(nodeIndex) - nodeIndex; + if (runLength < 3) { + stream.printf("%d", nodeIndex); + } else { + int endIndex = nodeIndex + runLength - 1; + stream.printf("%d-%d", nodeIndex, endIndex); + nodeIndex = endIndex; + } + } + } + stream.print(')'); + } + + /** + * Dump wheels in the specified vehicle. + * + * @param vehicle the vehicle to dump (not null, unaffected) + * @param indent (not null) + * @param numWheels the number of wheels to dump (>0) + */ + private void dumpWheels(PhysicsVehicle vehicle, String indent, + int numWheels) { + stream.print(':'); + PhysicsDescriber describer = getDescriber(); + String moreIndent = indent + indentIncrement(); + for (int wheelIndex = 0; wheelIndex < numWheels; ++wheelIndex) { + stream.printf("%n%s[%d] ", moreIndent, wheelIndex); + VehicleWheel wheel = vehicle.getWheel(wheelIndex); + String desc = describer.describe(wheel); + stream.print(desc); + + stream.printf("%n%s ", moreIndent); + desc = describer.describe2(wheel); + stream.print(desc); + + stream.print(" raycast="); + float raycast = vehicle.castRay(wheelIndex); + stream.print(MyString.describe(raycast)); + + if (raycast >= 0f) { + stream.print(" skid="); + float skid = wheel.getSkidInfo(); + stream.print(MyString.describe(skid)); + } + } + addLine(indent); + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/UserFilter.java b/MinieLibrary/src/main/java/jme3utilities/minie/UserFilter.java index 4dee1c2d5..0a607332c 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/UserFilter.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/UserFilter.java @@ -1,105 +1,105 @@ -/* - Copyright (c) 2019, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie; - -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsBody; -import java.util.logging.Logger; - -/** - * A simple DebugAppStateFilter that selects physics objects associated with a - * specific user object. Instances are immutable. - * - * @author Stephen Gold sgold@sonic.net - */ -public class UserFilter implements BulletDebugAppState.DebugAppStateFilter { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final private static Logger logger - = Logger.getLogger(UserFilter.class.getName()); - // ************************************************************************* - // fields - - /** - * user object (may be null) - */ - final private Object userObject; - // ************************************************************************* - // constructors - - /** - * Instantiate a filter for the specified user object. - * - * @param userObject the desired user, or null to display/dump objects with - * no user - */ - public UserFilter(Object userObject) { - this.userObject = userObject; - } - // ************************************************************************* - // DebugAppStateFilter methods - - /** - * Test whether the specified physics object should be displayed/dumped. - * - * @param physicsObject the joint or collision object to test (unaffected) - * @return return true if the object should be displayed/dumped, false if it - * shouldn't be - */ - @Override - public boolean displayObject(Object physicsObject) { - boolean result = false; - - if (physicsObject instanceof PhysicsCollisionObject) { - PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; - if (pco.getUserObject() == userObject) { - result = true; - } - - } else if (physicsObject instanceof PhysicsJoint) { - PhysicsJoint joint = (PhysicsJoint) physicsObject; - PhysicsBody a = joint.getBody(JointEnd.A); - if (a != null && a.getUserObject() == userObject) { - result = true; - } else { - PhysicsBody b = joint.getBody(JointEnd.B); - if (b != null && b.getUserObject() == userObject) { - result = true; - } - } - } - - return result; - } -} +/* + Copyright (c) 2019, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie; + +import com.jme3.bullet.collision.PhysicsCollisionObject; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.joints.PhysicsJoint; +import com.jme3.bullet.objects.PhysicsBody; +import java.util.logging.Logger; + +/** + * A simple DebugAppStateFilter that selects physics objects associated with a + * specific user object. Instances are immutable. + * + * @author Stephen Gold sgold@sonic.net + */ +public class UserFilter implements BulletDebugAppState.DebugAppStateFilter { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final private static Logger logger + = Logger.getLogger(UserFilter.class.getName()); + // ************************************************************************* + // fields + + /** + * user object (may be null) + */ + final private Object userObject; + // ************************************************************************* + // constructors + + /** + * Instantiate a filter for the specified user object. + * + * @param userObject the desired user, or null to display/dump objects with + * no user + */ + public UserFilter(Object userObject) { + this.userObject = userObject; + } + // ************************************************************************* + // DebugAppStateFilter methods + + /** + * Test whether the specified physics object should be displayed/dumped. + * + * @param physicsObject the joint or collision object to test (unaffected) + * @return return true if the object should be displayed/dumped, false if it + * shouldn't be + */ + @Override + public boolean displayObject(Object physicsObject) { + boolean result = false; + + if (physicsObject instanceof PhysicsCollisionObject) { + PhysicsCollisionObject pco = (PhysicsCollisionObject) physicsObject; + if (pco.getUserObject() == userObject) { + result = true; + } + + } else if (physicsObject instanceof PhysicsJoint) { + PhysicsJoint joint = (PhysicsJoint) physicsObject; + PhysicsBody a = joint.getBody(JointEnd.A); + if (a != null && a.getUserObject() == userObject) { + result = true; + } else { + PhysicsBody b = joint.getBody(JointEnd.B); + if (b != null && b.getUserObject() == userObject) { + result = true; + } + } + } + + return result; + } +} diff --git a/MinieLibrary/src/main/java/jme3utilities/minie/package-info.java b/MinieLibrary/src/main/java/jme3utilities/minie/package-info.java index 5ead8b695..c493483ec 100644 --- a/MinieLibrary/src/main/java/jme3utilities/minie/package-info.java +++ b/MinieLibrary/src/main/java/jme3utilities/minie/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2018, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Bullet physics support for jMonkeyEngine3. - */ -package jme3utilities.minie; +/* + Copyright (c) 2018, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Bullet physics support for jMonkeyEngine3. + */ +package jme3utilities.minie; diff --git a/MinieLibrary/src/main/java/vhacd/package-info.java b/MinieLibrary/src/main/java/vhacd/package-info.java index 616ba5733..e618f3388 100644 --- a/MinieLibrary/src/main/java/vhacd/package-info.java +++ b/MinieLibrary/src/main/java/vhacd/package-info.java @@ -1,5 +1,5 @@ -/** - * Java API for Khaled Mamou’s Volumetric-Hierarchical Approximate Convex - * Decomposition (V-HACD) algorithm. - */ -package vhacd; +/** + * Java API for Khaled Mamou’s Volumetric-Hierarchical Approximate Convex + * Decomposition (V-HACD) algorithm. + */ +package vhacd; diff --git a/MinieLibrary/src/main/java/vhacd4/package-info.java b/MinieLibrary/src/main/java/vhacd4/package-info.java index 46b6105f6..60ae7ba11 100644 --- a/MinieLibrary/src/main/java/vhacd4/package-info.java +++ b/MinieLibrary/src/main/java/vhacd4/package-info.java @@ -1,5 +1,5 @@ -/** - * Java API for version 4 of Khaled Mamou’s Volumetric-Hierarchical Approximate - * Convex Decomposition (V-HACD) algorithm. - */ -package vhacd4; +/** + * Java API for version 4 of Khaled Mamou’s Volumetric-Hierarchical Approximate + * Convex Decomposition (V-HACD) algorithm. + */ +package vhacd4; diff --git a/MinieLibrary/src/test/java/com/jme3/bullet/util/package-info.java b/MinieLibrary/src/test/java/com/jme3/bullet/util/package-info.java index 1b80780b2..ba140c3b6 100644 --- a/MinieLibrary/src/test/java/com/jme3/bullet/util/package-info.java +++ b/MinieLibrary/src/test/java/com/jme3/bullet/util/package-info.java @@ -1,35 +1,35 @@ -/* - * Copyright (c) 2022 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Automated tests for soft-body physics. - */ -package com.jme3.bullet.util; +/* + * Copyright (c) 2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Automated tests for soft-body physics. + */ +package com.jme3.bullet.util; diff --git a/MinieLibrary/src/test/java/jme3utilities/minie/test/package-info.java b/MinieLibrary/src/test/java/jme3utilities/minie/test/package-info.java index e96a027d1..390323655 100644 --- a/MinieLibrary/src/test/java/jme3utilities/minie/test/package-info.java +++ b/MinieLibrary/src/test/java/jme3utilities/minie/test/package-info.java @@ -1,30 +1,30 @@ -/* - Copyright (c) 2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Automated tests for the Minie library. - */ -package jme3utilities.minie.test; +/* + Copyright (c) 2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Automated tests for the Minie library. + */ +package jme3utilities.minie.test; diff --git a/README.md b/README.md index a49101cd1..de57fc3d5 100644 --- a/README.md +++ b/README.md @@ -1,459 +1,459 @@ -Minie Project logo - -The [Minie Project][project] is about improving the integration of -[Bullet real-time physics simulation][bullet] -and [Khaled Mamou's V-HACD Library][vhacd] into -[the jMonkeyEngine (JME) game engine][jme]. - -It contains 8 sub-projects: - - 1. MinieLibrary: the Minie runtime library and its automated tests - 2. [DacWizard]: a GUI application to configure a ragdoll - 3. VhacdTuner: a GUI application to tune the V-HACD algorithm for a particular mesh - 4. TutorialApps: tutorial apps - 5. MinieExamples: demos, examples, and non-automated test software - 6. MinieAssets: generate assets used in MinieExamples - 7. MinieDump: a command-line utility to dump J3O assets - 8. Jme3Examples: physics examples from jme3-examples - -Complete source code (in Java) is provided under -[a 3-clause BSD license][license]. - - - - -## Contents of this document - -+ [Why use Minie?](#why) -+ [Downloads](#downloads) -+ [Conventions](#conventions) -+ [Overview and design considerations](#overview) -+ [How to build Minie from source](#build) -+ [Tutorials](#tutorials) -+ [An overview of the demo applications](#demos) -+ [External links](#links) -+ [History](#history) -+ [Acknowledgments](#acks) - - - - -## Why use Minie? - -[jMonkeyEngine][jme] comes with `jme3-jbullet`, -its own Bullet integration library. -Why use Minie instead of `jme3-jbullet`? - - + Minie has many more features. (See the feature list below.) - + Minie fixes many bugs found in `jme3-jbullet`. - + Due to its shorter release cycle, future features and bug fixes - will probably appear first in Minie. - + Minie uses automated testing to reduce the risk of regressions and new bugs. - + Minie's classes are better encapsulated, with fewer public/protected fields - and less aliasing of small objects like vectors. This reduces the risk - of accidentally corrupting its internal data structures. - + Minie validates method arguments. This helps detect usage errors that - can lead to subtle bugs. - + Minie's source code is more readable and better documented. - -Summary of added features: - - + Extensions to `DynamicAnimControl` - + Soft-body simulation based on `btSoftBody` and `btSoftRigidDynamicsWorld`, - including anchors and soft-body joints - + Multi-body simulation based on `btMultiBody` and `btMultiBodyDynamicsWorld` - + Convex decomposition of meshes using [Khaled Mamou's V-HACD Library][vhacd], - including progress listeners - + `New6Dof` physics joints based on `btGeneric6DofSpring2Constraint` - + Alternative contact-and-constraint solvers based on `btDantzigSolver`, - `btLemkeSolver`, `btSolveProjectedGaussSeidel`, and `btNNCGConstraintSolver` - + collision shapes: - + `MultiSphere` shapes based on `btMultiSphereShape` - + `Box2dShape` shapes based on `btBox2dShape` - + `Convex2dShape` shapes based on `btConvex2dShape` - + `EmptyShape` shape based on `btEmptyShape` - + debugging aids: - + dump the contents of a `BulletAppState`, `PhysicsSpace`, - `CollisionShape`, or `MultiBody` - + visualize physics objects in multiple viewports - + customize debug material per collision object - + visualize the local axes, velocities, bounding boxes, CCD swept spheres, - and gravity vectors of collision objects - + visualize the children of compound collision shapes - + optional high-resolution debug meshes for convex shapes - + options to generate debug meshes that include indices, - normals (for shading), and/or texture coordinates (for texturing) - + all joints, shapes, collision objects, and multibodies - implement the `JmeCloneable` and `Comparable` interfaces - + enable/disable a `PhysicsJoint` - + single-ended physics joints - + ignore lists for collision objects - + application-specific data for collision objects - + access more parameters of rigid bodies, vehicles, characters, joints, - collision shapes, contact/constraint solvers, etcetera - + option to apply scaling with a `RigidBodyControl` - -Some jme3-jbullet classes that Minie omits: - - + `KinematicRagdollControl`, `HumanoidRagdollPreset`, and `RagdollPreset`: - use `DynamicAnimControl` instead - + `RagdollUtils`: not needed - -Other important differences: - - + `PhysicsSpace.addAll()` and `PhysicsSpace.removeAll()` add/remove collision - objects only; they do not add/remove joints. - + `RagdollCollisionListener` interface changed and moved - from the `com.jme3.bullet.collision` package - to the `com.jme3.bullet.animation` package. - -[Jump to table of contents](#toc) - - - - -## Downloads - -Newer releases (since v0.5.0) can be downloaded from -[GitHub](https://github.com/stephengold/Minie/releases). - -Older releases (v0.1.1 through v0.4.5) can be downloaded from -[the Jme3-utilities Project](https://github.com/stephengold/jme3-utilities/releases). - -Maven artifacts since v3.1.0 are available from -[MavenCentral](https://central.sonatype.com/search?q=Minie&namespace=com.github.stephengold). - -[Jump to table of contents](#toc) - - - - -## Conventions - -Package names begin with `jme3utilities.` -(if Stephen Gold holds the copyright) or -`com.jme3.`/`jme3test.` (if the jMonkeyEngine Project holds the copyright). - -The source code and pre-built libraries are compatible with JDK 8. - -[Jump to table of contents](#toc) - - - - -## Overview and design considerations - -### The role of physics simulation in games - -Most computer games don't require detailed physics simulation. - - + Canned animations usually suffice to illustrate characters walking, - jumping, and fighting. - + Detecting when a character enters a fixed zone - or comes into range of another character is a simple geometric calculation, - provided the zone or range has a box or sphere shape. - + For outer-space games, the equations of motion (Newton's 3rd Law) are easily - implemented from scratch. - -Other games require physics simulation, either because detailed physics is -integral to gameplay (as in bowling or auto racing) or else to enhance the -verisimilitude of effects such as collapsing buildings and/or people. -For such games, a real-time physics library such as Minie should prove useful. - -### How Minie works - -[How Minie works](https://stephengold.github.io/Minie/minie/implementation.html) - -### Computational efficiency - -The computational cost of collision detection grows rapidly with -the number of collision objects and the complexity of their shapes. -To simulate physics in real time, with modest CPUs, -it's vital to keep the physics simple: - - + Use very simple collision shapes (such as boxes, capsules, and spheres) - wherever possible. - + Minimize the number of collision objects by - merging static bodies together and - simulating only the most relevant moving bodies. - + Minimize the number of nodes in each soft body. - -### Scaling the world - -For a physics simulation, it might seem natural to choose kilograms and meters -as the units of mass and distance, respectively. -However, this is not a requirement, and for many games, -MKS units are not the best choice. - -Bullet documentation recommends that dynamic bodies have -masses as close as possible to 1. - -Also, to improve the performance and reliability of collision detection, -Bullet applies a margin to most collision objects. -By default, this margin is 0.04 physics-space units (psu). -While the margin is configurable, Bullet documentation -recommends against doing so. -For some collision shapes, margin increases the effective size of the object -and distorts its effective shape. -For this reason, it's undesirable to have a collision object -with any radius smaller than about 0.2 psu. - -Dynamic bodies in forced contact tend to jiggle. -Jiggling is mostly noticeable for sharp-edged bodies (such as boxes) -resting on uneven surfaces, under high gravity. -The higher the gravity (in psu per second squared), -the shorter the simulation time step (in seconds) needs to be. -For efficient and realistic simulation of Earth-like gravity (9.8 m/s) -with the default margin (0.04 psu) and time step (0.0167 seconds), -the psu should be 0.3 meters or larger. -This puts a soft lower limit on the dimensions (in psu) of dynamic bodies. - -Since Minie's debug visualization assumes that physics coordinates are -equivalent to world coordinates, these recommendations could impact -model creation and scene-graph design. -Physics units should therefore be chosen with care, -preferably early in the development process. - -[Jump to table of contents](#toc) - - - - -## How to build Minie from source - -[How to build Minie from source](https://stephengold.github.io/Minie/minie/build.html) - -[Jump to table of contents](#toc) - - - - - - - - - - - -## Tutorials - - + [How to add Minie to an existing project](https://stephengold.github.io/Minie/minie/minie-library-tutorials/add.html) - + [An introduction to rigid-body physics](https://stephengold.github.io/Minie/minie/minie-library-tutorials/rigidbody.html) - + [Choosing collision shapes](https://stephengold.github.io/Minie/minie/minie-library-tutorials/shape.html) - + [Debugging physics issues](https://stephengold.github.io/Minie/minie/minie-library-tutorials/debug.html) - + [An introduction to New6Dof](https://stephengold.github.io/Minie/minie/minie-library-tutorials/new6dof.html) - + [An introduction to DynamicAnimControl](https://stephengold.github.io/Minie/minie/minie-library-tutorials/dac.html) - + [Collision detection](https://stephengold.github.io/Minie/minie/minie-library-tutorials/collision.html) - + [An introduction to soft-body physics](https://stephengold.github.io/Minie/minie/minie-library-tutorials/softbody.html) - -[Jump to table of contents](#toc) - - - - -## An overview of the demo applications - -[An overview of the demo applications](https://stephengold.github.io/Minie/minie/demos.html) - -[Jump to table of contents](#toc) - - - - -## External links - -+ [the Minie Physics Library page](https://library.jmonkeyengine.org/#!entry=11511%2F38308161-c3cf-4e23-8754-528ca8387c11) - in [the JmonkeyEngine Library][library] -+ [The Bullet Physics SDK Manual](https://github.com/bulletphysics/bullet3/blob/master/docs/Bullet_User_Manual.pdf) -+ [The physics section of the jMonkeyEngine Wiki](https://wiki.jmonkeyengine.org/docs/3.4/physics/physics.html) -+ [Alan Chou's game-physics tutorial](http://allenchou.net/game-physics-series/) -+ [V-HACD v4 slideshow](https://docs.google.com/presentation/d/1OZ4mtZYrGEC8qffqb8F7Le2xzufiqvaPpRbLHKKgTIM) - -YouTube videos about Minie: - - + September 2022 teaser (splitting rigid bodies in real time) - [watch](https://www.youtube.com/watch?v=9IsCSgoKJeE) (0:53) - [source code](https://github.com/stephengold/Minie/blob/fd0a61d2d24f354e0a9418cfc904c5985b69cfd8/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemo.java) - + August 2022 walkthru of the VhacdTuner application - [watch](https://www.youtube.com/watch?v=iEWJtPujmM8) (7:45) - [source code](https://github.com/stephengold/Minie/blob/e1b7781fd06d8060ab96928379509a732fd9398f/VhacdTuner/src/main/java/jme3utilities/minie/tuner/VhacdTuner.java) - + June 2019 teaser #2 (rubber duck) - [watch](https://www.youtube.com/watch?v=GKc-_SqcpZo) (0:16) - [source code](https://github.com/stephengold/Minie/blob/d0326f636dbed76c809cb8ec654bfaf94786e988/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBodyControl.java) - + June 2019 teaser #1 (jogger in skirt) - [watch](https://www.youtube.com/watch?v=lLMBIASzAAM) (0:24) - [source code](https://github.com/stephengold/Minie/blob/40add685ec9243c3fa1e10f8b38b805a04a32863/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) - + May 2019 teaser #3 (wind-blown flag) - [watch](https://www.youtube.com/watch?v=7dcBr0j6sKw) (0:06) - [source code](https://github.com/stephengold/Minie/blob/9fb33ce21c5082af36ce2969daa79d63b57c0641/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) - + May 2019 teaser #2 (squishy ball and tablecloth) - [watch](https://www.youtube.com/watch?v=-ECGEe4CpcY) (0:12) - [source code](https://github.com/stephengold/Minie/blob/fe55f9baf83158d6347f765b4ff6bbf892056919/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) - + May 2019 teaser #1 (squishy ball) - [watch](https://www.youtube.com/watch?v=W3x4gdDn-Ko) (0:13) - [source code](https://github.com/stephengold/Minie/blob/b1a83f8a6440d8374f09258c6b1d471279833cfa/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) - + April 2019 walkthru of the DacWizard application - [watch](https://www.youtube.com/watch?v=iWyrzZe45jA) (8:12) - [source code](https://github.com/stephengold/Minie/blob/master/DacWizard/src/main/java/jme3utilities/minie/wizard/DacWizard.java) - + March 2019 short demo (IK for head/eye directions) - [watch](https://www.youtube.com/watch?v=8zquudx3A1A) (1:25) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/WatchDemo.java) - + March 2019 teaser (buoyancy) - [watch](https://www.youtube.com/watch?v=eq09m7pbk5A) (0:10) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/BuoyDemo.java) - + February 2019 demo (ropes) - [watch](https://www.youtube.com/watch?v=7PYDAyB5RCE) (4:47) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/RopeDemo.java) - + December 2018 demo (inverse kinematics) - [watch](https://www.youtube.com/watch?v=ZGqN9ZCCu-8) (6:27) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/BalanceDemo.java) - + December 2018 teaser (inverse kinematics) - [watch](https://www.youtube.com/watch?v=fTWQ9m47GIA) (0:51) - + November 2018 demo (single-ended joints) - [watch](https://www.youtube.com/watch?v=Mh9k5AfWzbg) (5:50) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/SeJointDemo.java) - + November 2018 demo (`MultiSphere` shape) - [watch](https://www.youtube.com/watch?v=OS2zjB01c6E) (0:13) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/MultiSphereDemo.java) - + October 2018 demo (`DynamicAnimControl` ragdolls) - [watch](https://www.youtube.com/watch?v=A1Rii99nb3Q) (2:49) - [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/TestDac.java) - -[Jump to table of contents](#toc) - - - - -## History - -The evolution of this project is chronicled in -[its release log][log]. - -Most of Minie was originally forked from `jme3-bullet`, -a library in the [jMonkeyEngine Game Engine][jme]. - -From January to November 2018, Minie was a sub-project of -[the Jme3-utilities Project][utilities]. - -Since November 2018, Minie has been a separate project, hosted at -[GitHub][minie]. - -[Jump to table of contents](#toc) - - - - -## Acknowledgments - -Like most projects, the Minie Project builds on the work of many who -have gone before. I therefore acknowledge the following -artists and software developers: - -+ Normen Hansen (aka "normen") for creating most of the `jme3-bullet` library - (on which Minie is based) and also for helpful insights -+ Rémy Bouquet (aka "nehon") for co-creating - `KinematicRagdollControl` (on which `DynamicAnimControl` is based), - for creating the Jaime model, and also for many helpful insights -+ Jules (aka "dokthar") for creating [the soft-body fork of jMonkeyEngine][dokthar] - from which Minie's soft-body support is derived -+ Khaled Mamou for creating and licensing the [V-HACD Library][vhacd] - for decomposing meshes into convex hulls -+ Riccardo Balbo (aka "riccardo") for creating and licensing - the [V-HACD Java Bindings Project][vhacdBindings] -+ "ndebruyn" for early testing of Minie on Android platforms -+ Pavly Gerges (aka "Pavl_G") for testing Minie on Raspberry Pi -+ Adam T. Ryder (aka "tryder") for creating and licensing - the [jME-TTF] rendering system -+ [Paul Speed (aka "pspeed42")][pspeed], for creating the [SimMath library][simmath] -+ "oxplay2", for reporting a `PhysicsRigidBody` bug and helping me pin it down -+ "duncanj", for pull request #15 -+ "qwq", for suggesting changes to how rigid-body contacts are managed - and for authoring the `ConveyorDemo` application -+ [Nathan Vegdahl][vegdahl], for creating the Puppet model -+ Tobias Jung, for distributing [ProFont] -+ plus the creators of (and contributors to) the following software: - + the [Antora] static website generator - + the [Blender] 3-D animation suite - + the [Bullet] real-time physics library - + the [Checkstyle] tool - + the [FindBugs] source-code analyzer - + the [Firefox] and [Google Chrome][chrome] web browsers - + the [Git] revision-control system and GitK commit viewer - + the [GitKraken] client - + the [GNU Project Debugger][gdb] - + the [Gradle] build tool - + the [IntelliJ IDEA][idea] and [NetBeans] integrated development environments - + the [Java] compiler, standard doclet, and runtime environment - + [jMonkeyEngine][jme] and the jME3 Software Development Kit - + the [Linux Mint][mint] operating system - + LWJGL, the Lightweight Java Game Library - + the [MakeHuman] 3-D character creation tool - + the [Markdown] document-conversion tool - + the [Meld] visual merge tool - + Microsoft Windows - + the [Nifty] graphical user-interface library - + [Open Broadcaster Software Studio][obs] - + the PMD source-code analyzer - + [ProFont], the programmers' font - + the [WinMerge] differencing and merging tool - -I am grateful to [GitHub], [Sonatype], [JFrog], -[Travis], [MacStadium], [YouTube], and [Imgur] -for providing free hosting for this project -and many other open-source projects. - -I'm also grateful to my dear Holly, for keeping me sane. - -If I've misattributed anything or left anyone out, please let me know, so I can -correct the situation: sgold@sonic.net - -[Jump to table of contents](#toc) - - -[antora]: https://antora.org/ "Antora Project" -[blender]: https://docs.blender.org "Blender Project" -[bullet]: https://pybullet.org/wordpress "Bullet Real-Time Physics Simulation" -[checkstyle]: https://checkstyle.org "Checkstyle" -[chrome]: https://www.google.com/chrome "Chrome" -[dacwizard]: https://github.com/stephengold/Minie/tree/master/DacWizard "DacWizard Application" -[dokthar]: https://github.com/dokthar/jmonkeyengine "Dokthar's fork of JMonkeyEngine" -[elements]: https://www.adobe.com/products/photoshop-elements.html "Photoshop Elements" -[findbugs]: http://findbugs.sourceforge.net "FindBugs Project" -[firefox]: https://www.mozilla.org/en-US/firefox "Firefox" -[gdb]: https://www.gnu.org/software/gdb/ "GNU Project Debugger" -[git]: https://git-scm.com "Git" -[github]: https://github.com "GitHub" -[gitkraken]: https://www.gitkraken.com "GitKraken client" -[gradle]: https://gradle.org "Gradle Project" -[heart]: https://github.com/stephengold/Heart "Heart Project" -[idea]: https://www.jetbrains.com/idea/ "IntelliJ IDEA" -[imgur]: https://imgur.com/ "Imgur" -[java]: https://java.com "Java" -[jfrog]: https://www.jfrog.com "JFrog" -[jme]: https://jmonkeyengine.org "jMonkeyEngine Project" -[jme-ttf]: https://1337atr.weebly.com/jttf.html "jME-TTF Rendering System" -[library]: https://library.jmonkeyengine.org "jMonkeyEngine Library" -[license]: https://github.com/stephengold/Minie/blob/master/LICENSE "Minie license" -[log]: https://github.com/stephengold/Minie/blob/master/MinieLibrary/release-notes.md "release log" -[macstadium]: https://www.macstadium.com/ "MacStadium" -[makehuman]: http://www.makehumancommunity.org/ "MakeHuman Community" -[manual]: https://github.com/bulletphysics/bullet3/blob/master/docs/Bullet_User_Manual.pdf "Bullet User Manual" -[markdown]: https://daringfireball.net/projects/markdown "Markdown Project" -[meld]: https://meldmerge.org "Meld merge tool" -[minie]: https://stephengold.github.io/Minie/minie/overview.html "Minie Project" -[mint]: https://linuxmint.com "Linux Mint Project" -[netbeans]: https://netbeans.org "NetBeans Project" -[nifty]: https://nifty-gui.github.io/nifty-gui "Nifty GUI Project" -[obs]: https://obsproject.com "Open Broadcaster Software Project" -[profont]: https://tobiasjung.name/profont "ProFont Project" -[project]: https://stephengold.github.io/Minie "Minie Project" -[simmath]: https://github.com/Simsilica/SimMath "SimMath Library" -[sonatype]: https://www.sonatype.com "Sonatype" -[travis]: https://travis-ci.com "Travis CI" -[utilities]: https://github.com/stephengold/jme3-utilities "Jme3-utilities Project" -[vegdahl]: https://www.cessen.com "Nathan Vegdahl" -[vhacd]: https://github.com/kmammou/v-hacd "V-HACD Library" -[vhacdBindings]: https://github.com/riccardobl/v-hacd-java-bindings "Riccardo's V-hacd-java-bindings Project" -[wes]: https://github.com/stephengold/Wes "Wes Project" -[winmerge]: https://winmerge.org "WinMerge Project" -[youtube]: https://www.youtube.com/ "YouTube" +Minie Project logo + +The [Minie Project][project] is about improving the integration of +[Bullet real-time physics simulation][bullet] +and [Khaled Mamou's V-HACD Library][vhacd] into +[the jMonkeyEngine (JME) game engine][jme]. + +It contains 8 sub-projects: + + 1. MinieLibrary: the Minie runtime library and its automated tests + 2. [DacWizard]: a GUI application to configure a ragdoll + 3. VhacdTuner: a GUI application to tune the V-HACD algorithm for a particular mesh + 4. TutorialApps: tutorial apps + 5. MinieExamples: demos, examples, and non-automated test software + 6. MinieAssets: generate assets used in MinieExamples + 7. MinieDump: a command-line utility to dump J3O assets + 8. Jme3Examples: physics examples from jme3-examples + +Complete source code (in Java) is provided under +[a 3-clause BSD license][license]. + + + + +## Contents of this document + ++ [Why use Minie?](#why) ++ [Downloads](#downloads) ++ [Conventions](#conventions) ++ [Overview and design considerations](#overview) ++ [How to build Minie from source](#build) ++ [Tutorials](#tutorials) ++ [An overview of the demo applications](#demos) ++ [External links](#links) ++ [History](#history) ++ [Acknowledgments](#acks) + + + + +## Why use Minie? + +[jMonkeyEngine][jme] comes with `jme3-jbullet`, +its own Bullet integration library. +Why use Minie instead of `jme3-jbullet`? + + + Minie has many more features. (See the feature list below.) + + Minie fixes many bugs found in `jme3-jbullet`. + + Due to its shorter release cycle, future features and bug fixes + will probably appear first in Minie. + + Minie uses automated testing to reduce the risk of regressions and new bugs. + + Minie's classes are better encapsulated, with fewer public/protected fields + and less aliasing of small objects like vectors. This reduces the risk + of accidentally corrupting its internal data structures. + + Minie validates method arguments. This helps detect usage errors that + can lead to subtle bugs. + + Minie's source code is more readable and better documented. + +Summary of added features: + + + Extensions to `DynamicAnimControl` + + Soft-body simulation based on `btSoftBody` and `btSoftRigidDynamicsWorld`, + including anchors and soft-body joints + + Multi-body simulation based on `btMultiBody` and `btMultiBodyDynamicsWorld` + + Convex decomposition of meshes using [Khaled Mamou's V-HACD Library][vhacd], + including progress listeners + + `New6Dof` physics joints based on `btGeneric6DofSpring2Constraint` + + Alternative contact-and-constraint solvers based on `btDantzigSolver`, + `btLemkeSolver`, `btSolveProjectedGaussSeidel`, and `btNNCGConstraintSolver` + + collision shapes: + + `MultiSphere` shapes based on `btMultiSphereShape` + + `Box2dShape` shapes based on `btBox2dShape` + + `Convex2dShape` shapes based on `btConvex2dShape` + + `EmptyShape` shape based on `btEmptyShape` + + debugging aids: + + dump the contents of a `BulletAppState`, `PhysicsSpace`, + `CollisionShape`, or `MultiBody` + + visualize physics objects in multiple viewports + + customize debug material per collision object + + visualize the local axes, velocities, bounding boxes, CCD swept spheres, + and gravity vectors of collision objects + + visualize the children of compound collision shapes + + optional high-resolution debug meshes for convex shapes + + options to generate debug meshes that include indices, + normals (for shading), and/or texture coordinates (for texturing) + + all joints, shapes, collision objects, and multibodies + implement the `JmeCloneable` and `Comparable` interfaces + + enable/disable a `PhysicsJoint` + + single-ended physics joints + + ignore lists for collision objects + + application-specific data for collision objects + + access more parameters of rigid bodies, vehicles, characters, joints, + collision shapes, contact/constraint solvers, etcetera + + option to apply scaling with a `RigidBodyControl` + +Some jme3-jbullet classes that Minie omits: + + + `KinematicRagdollControl`, `HumanoidRagdollPreset`, and `RagdollPreset`: + use `DynamicAnimControl` instead + + `RagdollUtils`: not needed + +Other important differences: + + + `PhysicsSpace.addAll()` and `PhysicsSpace.removeAll()` add/remove collision + objects only; they do not add/remove joints. + + `RagdollCollisionListener` interface changed and moved + from the `com.jme3.bullet.collision` package + to the `com.jme3.bullet.animation` package. + +[Jump to table of contents](#toc) + + + + +## Downloads + +Newer releases (since v0.5.0) can be downloaded from +[GitHub](https://github.com/stephengold/Minie/releases). + +Older releases (v0.1.1 through v0.4.5) can be downloaded from +[the Jme3-utilities Project](https://github.com/stephengold/jme3-utilities/releases). + +Maven artifacts since v3.1.0 are available from +[MavenCentral](https://central.sonatype.com/search?q=Minie&namespace=com.github.stephengold). + +[Jump to table of contents](#toc) + + + + +## Conventions + +Package names begin with `jme3utilities.` +(if Stephen Gold holds the copyright) or +`com.jme3.`/`jme3test.` (if the jMonkeyEngine Project holds the copyright). + +The source code and pre-built libraries are compatible with JDK 8. + +[Jump to table of contents](#toc) + + + + +## Overview and design considerations + +### The role of physics simulation in games + +Most computer games don't require detailed physics simulation. + + + Canned animations usually suffice to illustrate characters walking, + jumping, and fighting. + + Detecting when a character enters a fixed zone + or comes into range of another character is a simple geometric calculation, + provided the zone or range has a box or sphere shape. + + For outer-space games, the equations of motion (Newton's 3rd Law) are easily + implemented from scratch. + +Other games require physics simulation, either because detailed physics is +integral to gameplay (as in bowling or auto racing) or else to enhance the +verisimilitude of effects such as collapsing buildings and/or people. +For such games, a real-time physics library such as Minie should prove useful. + +### How Minie works + +[How Minie works](https://stephengold.github.io/Minie/minie/implementation.html) + +### Computational efficiency + +The computational cost of collision detection grows rapidly with +the number of collision objects and the complexity of their shapes. +To simulate physics in real time, with modest CPUs, +it's vital to keep the physics simple: + + + Use very simple collision shapes (such as boxes, capsules, and spheres) + wherever possible. + + Minimize the number of collision objects by + merging static bodies together and + simulating only the most relevant moving bodies. + + Minimize the number of nodes in each soft body. + +### Scaling the world + +For a physics simulation, it might seem natural to choose kilograms and meters +as the units of mass and distance, respectively. +However, this is not a requirement, and for many games, +MKS units are not the best choice. + +Bullet documentation recommends that dynamic bodies have +masses as close as possible to 1. + +Also, to improve the performance and reliability of collision detection, +Bullet applies a margin to most collision objects. +By default, this margin is 0.04 physics-space units (psu). +While the margin is configurable, Bullet documentation +recommends against doing so. +For some collision shapes, margin increases the effective size of the object +and distorts its effective shape. +For this reason, it's undesirable to have a collision object +with any radius smaller than about 0.2 psu. + +Dynamic bodies in forced contact tend to jiggle. +Jiggling is mostly noticeable for sharp-edged bodies (such as boxes) +resting on uneven surfaces, under high gravity. +The higher the gravity (in psu per second squared), +the shorter the simulation time step (in seconds) needs to be. +For efficient and realistic simulation of Earth-like gravity (9.8 m/s) +with the default margin (0.04 psu) and time step (0.0167 seconds), +the psu should be 0.3 meters or larger. +This puts a soft lower limit on the dimensions (in psu) of dynamic bodies. + +Since Minie's debug visualization assumes that physics coordinates are +equivalent to world coordinates, these recommendations could impact +model creation and scene-graph design. +Physics units should therefore be chosen with care, +preferably early in the development process. + +[Jump to table of contents](#toc) + + + + +## How to build Minie from source + +[How to build Minie from source](https://stephengold.github.io/Minie/minie/build.html) + +[Jump to table of contents](#toc) + + + + + + + + + + + +## Tutorials + + + [How to add Minie to an existing project](https://stephengold.github.io/Minie/minie/minie-library-tutorials/add.html) + + [An introduction to rigid-body physics](https://stephengold.github.io/Minie/minie/minie-library-tutorials/rigidbody.html) + + [Choosing collision shapes](https://stephengold.github.io/Minie/minie/minie-library-tutorials/shape.html) + + [Debugging physics issues](https://stephengold.github.io/Minie/minie/minie-library-tutorials/debug.html) + + [An introduction to New6Dof](https://stephengold.github.io/Minie/minie/minie-library-tutorials/new6dof.html) + + [An introduction to DynamicAnimControl](https://stephengold.github.io/Minie/minie/minie-library-tutorials/dac.html) + + [Collision detection](https://stephengold.github.io/Minie/minie/minie-library-tutorials/collision.html) + + [An introduction to soft-body physics](https://stephengold.github.io/Minie/minie/minie-library-tutorials/softbody.html) + +[Jump to table of contents](#toc) + + + + +## An overview of the demo applications + +[An overview of the demo applications](https://stephengold.github.io/Minie/minie/demos.html) + +[Jump to table of contents](#toc) + + + + +## External links + ++ [the Minie Physics Library page](https://library.jmonkeyengine.org/#!entry=11511%2F38308161-c3cf-4e23-8754-528ca8387c11) + in [the JmonkeyEngine Library][library] ++ [The Bullet Physics SDK Manual](https://github.com/bulletphysics/bullet3/blob/master/docs/Bullet_User_Manual.pdf) ++ [The physics section of the jMonkeyEngine Wiki](https://wiki.jmonkeyengine.org/docs/3.4/physics/physics.html) ++ [Alan Chou's game-physics tutorial](http://allenchou.net/game-physics-series/) ++ [V-HACD v4 slideshow](https://docs.google.com/presentation/d/1OZ4mtZYrGEC8qffqb8F7Le2xzufiqvaPpRbLHKKgTIM) + +YouTube videos about Minie: + + + September 2022 teaser (splitting rigid bodies in real time) + [watch](https://www.youtube.com/watch?v=9IsCSgoKJeE) (0:53) + [source code](https://github.com/stephengold/Minie/blob/fd0a61d2d24f354e0a9418cfc904c5985b69cfd8/MinieExamples/src/main/java/jme3utilities/minie/test/SplitDemo.java) + + August 2022 walkthru of the VhacdTuner application + [watch](https://www.youtube.com/watch?v=iEWJtPujmM8) (7:45) + [source code](https://github.com/stephengold/Minie/blob/e1b7781fd06d8060ab96928379509a732fd9398f/VhacdTuner/src/main/java/jme3utilities/minie/tuner/VhacdTuner.java) + + June 2019 teaser #2 (rubber duck) + [watch](https://www.youtube.com/watch?v=GKc-_SqcpZo) (0:16) + [source code](https://github.com/stephengold/Minie/blob/d0326f636dbed76c809cb8ec654bfaf94786e988/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBodyControl.java) + + June 2019 teaser #1 (jogger in skirt) + [watch](https://www.youtube.com/watch?v=lLMBIASzAAM) (0:24) + [source code](https://github.com/stephengold/Minie/blob/40add685ec9243c3fa1e10f8b38b805a04a32863/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) + + May 2019 teaser #3 (wind-blown flag) + [watch](https://www.youtube.com/watch?v=7dcBr0j6sKw) (0:06) + [source code](https://github.com/stephengold/Minie/blob/9fb33ce21c5082af36ce2969daa79d63b57c0641/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) + + May 2019 teaser #2 (squishy ball and tablecloth) + [watch](https://www.youtube.com/watch?v=-ECGEe4CpcY) (0:12) + [source code](https://github.com/stephengold/Minie/blob/fe55f9baf83158d6347f765b4ff6bbf892056919/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) + + May 2019 teaser #1 (squishy ball) + [watch](https://www.youtube.com/watch?v=W3x4gdDn-Ko) (0:13) + [source code](https://github.com/stephengold/Minie/blob/b1a83f8a6440d8374f09258c6b1d471279833cfa/MinieExamples/src/main/java/jme3utilities/minie/test/TestSoftBody.java) + + April 2019 walkthru of the DacWizard application + [watch](https://www.youtube.com/watch?v=iWyrzZe45jA) (8:12) + [source code](https://github.com/stephengold/Minie/blob/master/DacWizard/src/main/java/jme3utilities/minie/wizard/DacWizard.java) + + March 2019 short demo (IK for head/eye directions) + [watch](https://www.youtube.com/watch?v=8zquudx3A1A) (1:25) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/WatchDemo.java) + + March 2019 teaser (buoyancy) + [watch](https://www.youtube.com/watch?v=eq09m7pbk5A) (0:10) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/BuoyDemo.java) + + February 2019 demo (ropes) + [watch](https://www.youtube.com/watch?v=7PYDAyB5RCE) (4:47) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/RopeDemo.java) + + December 2018 demo (inverse kinematics) + [watch](https://www.youtube.com/watch?v=ZGqN9ZCCu-8) (6:27) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/BalanceDemo.java) + + December 2018 teaser (inverse kinematics) + [watch](https://www.youtube.com/watch?v=fTWQ9m47GIA) (0:51) + + November 2018 demo (single-ended joints) + [watch](https://www.youtube.com/watch?v=Mh9k5AfWzbg) (5:50) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/SeJointDemo.java) + + November 2018 demo (`MultiSphere` shape) + [watch](https://www.youtube.com/watch?v=OS2zjB01c6E) (0:13) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/MultiSphereDemo.java) + + October 2018 demo (`DynamicAnimControl` ragdolls) + [watch](https://www.youtube.com/watch?v=A1Rii99nb3Q) (2:49) + [source code](https://github.com/stephengold/Minie/blob/master/MinieExamples/src/main/java/jme3utilities/minie/test/TestDac.java) + +[Jump to table of contents](#toc) + + + + +## History + +The evolution of this project is chronicled in +[its release log][log]. + +Most of Minie was originally forked from `jme3-bullet`, +a library in the [jMonkeyEngine Game Engine][jme]. + +From January to November 2018, Minie was a sub-project of +[the Jme3-utilities Project][utilities]. + +Since November 2018, Minie has been a separate project, hosted at +[GitHub][minie]. + +[Jump to table of contents](#toc) + + + + +## Acknowledgments + +Like most projects, the Minie Project builds on the work of many who +have gone before. I therefore acknowledge the following +artists and software developers: + ++ Normen Hansen (aka "normen") for creating most of the `jme3-bullet` library + (on which Minie is based) and also for helpful insights ++ Rémy Bouquet (aka "nehon") for co-creating + `KinematicRagdollControl` (on which `DynamicAnimControl` is based), + for creating the Jaime model, and also for many helpful insights ++ Jules (aka "dokthar") for creating [the soft-body fork of jMonkeyEngine][dokthar] + from which Minie's soft-body support is derived ++ Khaled Mamou for creating and licensing the [V-HACD Library][vhacd] + for decomposing meshes into convex hulls ++ Riccardo Balbo (aka "riccardo") for creating and licensing + the [V-HACD Java Bindings Project][vhacdBindings] ++ "ndebruyn" for early testing of Minie on Android platforms ++ Pavly Gerges (aka "Pavl_G") for testing Minie on Raspberry Pi ++ Adam T. Ryder (aka "tryder") for creating and licensing + the [jME-TTF] rendering system ++ [Paul Speed (aka "pspeed42")][pspeed], for creating the [SimMath library][simmath] ++ "oxplay2", for reporting a `PhysicsRigidBody` bug and helping me pin it down ++ "duncanj", for pull request #15 ++ "qwq", for suggesting changes to how rigid-body contacts are managed + and for authoring the `ConveyorDemo` application ++ [Nathan Vegdahl][vegdahl], for creating the Puppet model ++ Tobias Jung, for distributing [ProFont] ++ plus the creators of (and contributors to) the following software: + + the [Antora] static website generator + + the [Blender] 3-D animation suite + + the [Bullet] real-time physics library + + the [Checkstyle] tool + + the [FindBugs] source-code analyzer + + the [Firefox] and [Google Chrome][chrome] web browsers + + the [Git] revision-control system and GitK commit viewer + + the [GitKraken] client + + the [GNU Project Debugger][gdb] + + the [Gradle] build tool + + the [IntelliJ IDEA][idea] and [NetBeans] integrated development environments + + the [Java] compiler, standard doclet, and runtime environment + + [jMonkeyEngine][jme] and the jME3 Software Development Kit + + the [Linux Mint][mint] operating system + + LWJGL, the Lightweight Java Game Library + + the [MakeHuman] 3-D character creation tool + + the [Markdown] document-conversion tool + + the [Meld] visual merge tool + + Microsoft Windows + + the [Nifty] graphical user-interface library + + [Open Broadcaster Software Studio][obs] + + the PMD source-code analyzer + + [ProFont], the programmers' font + + the [WinMerge] differencing and merging tool + +I am grateful to [GitHub], [Sonatype], [JFrog], +[Travis], [MacStadium], [YouTube], and [Imgur] +for providing free hosting for this project +and many other open-source projects. + +I'm also grateful to my dear Holly, for keeping me sane. + +If I've misattributed anything or left anyone out, please let me know, so I can +correct the situation: sgold@sonic.net + +[Jump to table of contents](#toc) + + +[antora]: https://antora.org/ "Antora Project" +[blender]: https://docs.blender.org "Blender Project" +[bullet]: https://pybullet.org/wordpress "Bullet Real-Time Physics Simulation" +[checkstyle]: https://checkstyle.org "Checkstyle" +[chrome]: https://www.google.com/chrome "Chrome" +[dacwizard]: https://github.com/stephengold/Minie/tree/master/DacWizard "DacWizard Application" +[dokthar]: https://github.com/dokthar/jmonkeyengine "Dokthar's fork of JMonkeyEngine" +[elements]: https://www.adobe.com/products/photoshop-elements.html "Photoshop Elements" +[findbugs]: http://findbugs.sourceforge.net "FindBugs Project" +[firefox]: https://www.mozilla.org/en-US/firefox "Firefox" +[gdb]: https://www.gnu.org/software/gdb/ "GNU Project Debugger" +[git]: https://git-scm.com "Git" +[github]: https://github.com "GitHub" +[gitkraken]: https://www.gitkraken.com "GitKraken client" +[gradle]: https://gradle.org "Gradle Project" +[heart]: https://github.com/stephengold/Heart "Heart Project" +[idea]: https://www.jetbrains.com/idea/ "IntelliJ IDEA" +[imgur]: https://imgur.com/ "Imgur" +[java]: https://java.com "Java" +[jfrog]: https://www.jfrog.com "JFrog" +[jme]: https://jmonkeyengine.org "jMonkeyEngine Project" +[jme-ttf]: https://1337atr.weebly.com/jttf.html "jME-TTF Rendering System" +[library]: https://library.jmonkeyengine.org "jMonkeyEngine Library" +[license]: https://github.com/stephengold/Minie/blob/master/LICENSE "Minie license" +[log]: https://github.com/stephengold/Minie/blob/master/MinieLibrary/release-notes.md "release log" +[macstadium]: https://www.macstadium.com/ "MacStadium" +[makehuman]: http://www.makehumancommunity.org/ "MakeHuman Community" +[manual]: https://github.com/bulletphysics/bullet3/blob/master/docs/Bullet_User_Manual.pdf "Bullet User Manual" +[markdown]: https://daringfireball.net/projects/markdown "Markdown Project" +[meld]: https://meldmerge.org "Meld merge tool" +[minie]: https://stephengold.github.io/Minie/minie/overview.html "Minie Project" +[mint]: https://linuxmint.com "Linux Mint Project" +[netbeans]: https://netbeans.org "NetBeans Project" +[nifty]: https://nifty-gui.github.io/nifty-gui "Nifty GUI Project" +[obs]: https://obsproject.com "Open Broadcaster Software Project" +[profont]: https://tobiasjung.name/profont "ProFont Project" +[project]: https://stephengold.github.io/Minie "Minie Project" +[simmath]: https://github.com/Simsilica/SimMath "SimMath Library" +[sonatype]: https://www.sonatype.com "Sonatype" +[travis]: https://travis-ci.com "Travis CI" +[utilities]: https://github.com/stephengold/jme3-utilities "Jme3-utilities Project" +[vegdahl]: https://www.cessen.com "Nathan Vegdahl" +[vhacd]: https://github.com/kmammou/v-hacd "V-HACD Library" +[vhacdBindings]: https://github.com/riccardobl/v-hacd-java-bindings "Riccardo's V-hacd-java-bindings Project" +[wes]: https://github.com/stephengold/Wes "Wes Project" +[winmerge]: https://winmerge.org "WinMerge Project" +[youtube]: https://www.youtube.com/ "YouTube" diff --git a/TutorialApps/build.gradle b/TutorialApps/build.gradle index d2acabf4c..fd20522eb 100644 --- a/TutorialApps/build.gradle +++ b/TutorialApps/build.gradle @@ -1,38 +1,38 @@ -// Note: "common.gradle" in the root project contains additional initialization -// for this project. This initialization is applied in the "build.gradle" -// of the root project. - -plugins { - id 'application' -} - -tasks.withType(JavaCompile) { // Java compile-time options: - options.deprecation = true -} - -application { - mainClass = 'jme3utilities.tutorial.HelloRigidBody' -} -if (!hasProperty('mainClass')) { - ext.mainClass = application.mainClass -} -jar.manifest.attributes('Main-Class': application.mainClass) - -dependencies { - implementation 'org.jmonkeyengine:jme3-desktop:' + jme3Version - runtimeOnly 'org.jmonkeyengine:jme3-plugins:' + jme3Version - runtimeOnly testdataCoordinates // for MonkeyHead, Ninja, and Oto - - // select one version of LWJGL: - //runtimeOnly 'org.jmonkeyengine:jme3-lwjgl:' + jme3Version // for LWJGL 2.x - runtimeOnly 'org.jmonkeyengine:jme3-lwjgl3:' + jme3Version // for LWJGL 3.x - - // TutorialApps doesn't use jme3-jogg - // -- it is included solely to avoid warnings from AssetConfig. - runtimeOnly 'org.jmonkeyengine:jme3-jogg:' + jme3Version - - //implementation 'com.github.stephengold:Minie:' + minieVersion // for published library - implementation project(':MinieLibrary') // for latest sourcecode -} - -startScripts.dependsOn(':MinieLibrary:assemble') +// Note: "common.gradle" in the root project contains additional initialization +// for this project. This initialization is applied in the "build.gradle" +// of the root project. + +plugins { + id 'application' +} + +tasks.withType(JavaCompile) { // Java compile-time options: + options.deprecation = true +} + +application { + mainClass = 'jme3utilities.tutorial.HelloRigidBody' +} +if (!hasProperty('mainClass')) { + ext.mainClass = application.mainClass +} +jar.manifest.attributes('Main-Class': application.mainClass) + +dependencies { + implementation 'org.jmonkeyengine:jme3-desktop:' + jme3Version + runtimeOnly 'org.jmonkeyengine:jme3-plugins:' + jme3Version + runtimeOnly testdataCoordinates // for MonkeyHead, Ninja, and Oto + + // select one version of LWJGL: + //runtimeOnly 'org.jmonkeyengine:jme3-lwjgl:' + jme3Version // for LWJGL 2.x + runtimeOnly 'org.jmonkeyengine:jme3-lwjgl3:' + jme3Version // for LWJGL 3.x + + // TutorialApps doesn't use jme3-jogg + // -- it is included solely to avoid warnings from AssetConfig. + runtimeOnly 'org.jmonkeyengine:jme3-jogg:' + jme3Version + + //implementation 'com.github.stephengold:Minie:' + minieVersion // for published library + implementation project(':MinieLibrary') // for latest sourcecode +} + +startScripts.dependsOn(':MinieLibrary:assemble') diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloApplyScale.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloApplyScale.java index 4e4fe8e67..ef9b6e7e8 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloApplyScale.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloApplyScale.java @@ -1,187 +1,187 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.ViewPort; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; -import com.jme3.system.AppSettings; - -/** - * A simple example of a kinematic RigidBodyControl with setApplyScale(true). - *

- * Builds upon HelloKinematicRbc. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloApplyScale extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * physics-simulation time (in seconds, ≥0) - */ - private static float elapsedTime = 0f; - /** - * cube geometry, varying in size - */ - private static Geometry cubeGeometry; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloApplyScale application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloApplyScale application = new HelloApplyScale(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - PhysicsSpace physicsSpace = configurePhysics(); - - // Create the cube Geometry and add it to the scene graph. - Material cubeMaterial = new Material(assetManager, Materials.LIGHTING); - Mesh cubeMesh = new Box(1f, 1f, 1f); - cubeGeometry = new Geometry("kine", cubeMesh); - cubeGeometry.setMaterial(cubeMaterial); - rootNode.attachChild(cubeGeometry); - - // Create an RBC and add it to the Geometry. - float mass = 2f; - RigidBodyControl kineRbc = new RigidBodyControl(mass); - cubeGeometry.addControl(kineRbc); - - // Add the control to the space. - kineRbc.setPhysicsSpace(physicsSpace); - - // Set the kinematic and "apply scale" flags on the RBC. - kineRbc.setKinematic(true); - kineRbc.setApplyScale(true); - - addLighting(rootNode); - configureCamera(); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Vary the scale of the Geometry with time. - float cycleTime = 3f; // seconds - float phaseAngle = elapsedTime * FastMath.TWO_PI / cycleTime; - - float scaleFactor = 1f + 0.5f * FastMath.sin(phaseAngle); - Vector3f scale = Vector3f.UNIT_XYZ.mult(scaleFactor); - cubeGeometry.setLocalScale(scale); - - elapsedTime += tpf; - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private static void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - } - - /** - * Configure the camera during startup (for a better view). - */ - private void configureCamera() { - cam.setLocation(new Vector3f(1f, 1.452773f, 10.1f)); - cam.setRotation(new Quaternion(0f, 0.99891f, -0.043f, -0.017f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Direct debug visuals to a post ViewPort that clears the depth buffer. - // This prevents z-fighting between the box and its debug visuals. - ViewPort overlay = renderManager.createPostView("Overlay", cam); - overlay.setClearFlags(false, true, false); - bulletAppState.setDebugViewPorts(overlay); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; + +/** + * A simple example of a kinematic RigidBodyControl with setApplyScale(true). + *

+ * Builds upon HelloKinematicRbc. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloApplyScale extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * physics-simulation time (in seconds, ≥0) + */ + private static float elapsedTime = 0f; + /** + * cube geometry, varying in size + */ + private static Geometry cubeGeometry; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloApplyScale application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloApplyScale application = new HelloApplyScale(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + PhysicsSpace physicsSpace = configurePhysics(); + + // Create the cube Geometry and add it to the scene graph. + Material cubeMaterial = new Material(assetManager, Materials.LIGHTING); + Mesh cubeMesh = new Box(1f, 1f, 1f); + cubeGeometry = new Geometry("kine", cubeMesh); + cubeGeometry.setMaterial(cubeMaterial); + rootNode.attachChild(cubeGeometry); + + // Create an RBC and add it to the Geometry. + float mass = 2f; + RigidBodyControl kineRbc = new RigidBodyControl(mass); + cubeGeometry.addControl(kineRbc); + + // Add the control to the space. + kineRbc.setPhysicsSpace(physicsSpace); + + // Set the kinematic and "apply scale" flags on the RBC. + kineRbc.setKinematic(true); + kineRbc.setApplyScale(true); + + addLighting(rootNode); + configureCamera(); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Vary the scale of the Geometry with time. + float cycleTime = 3f; // seconds + float phaseAngle = elapsedTime * FastMath.TWO_PI / cycleTime; + + float scaleFactor = 1f + 0.5f * FastMath.sin(phaseAngle); + Vector3f scale = Vector3f.UNIT_XYZ.mult(scaleFactor); + cubeGeometry.setLocalScale(scale); + + elapsedTime += tpf; + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private static void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + } + + /** + * Configure the camera during startup (for a better view). + */ + private void configureCamera() { + cam.setLocation(new Vector3f(1f, 1.452773f, 10.1f)); + cam.setRotation(new Quaternion(0f, 0.99891f, -0.043f, -0.017f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Direct debug visuals to a post ViewPort that clears the depth buffer. + // This prevents z-fighting between the box and its debug visuals. + ViewPort overlay = renderManager.createPostView("Overlay", cam); + overlay.setClearFlags(false, true, false); + bulletAppState.setDebugViewPorts(overlay); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloBoneLink.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloBoneLink.java index 067a0c5d2..9add1527f 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloBoneLink.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloBoneLink.java @@ -1,160 +1,160 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.bullet.animation.LinkConfig; -import com.jme3.bullet.animation.RangeOfMotion; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.InputListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; - -/** - * A simple example of a DynamicAnimControl with 3 bone links. - *

- * Builds upon HelloDac. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloBoneLink extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloBoneLink application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloBoneLink application = new HelloBoneLink(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - physicsSpace = bulletAppState.getPhysicsSpace(); - - // Add a box to the scene and relocate the camera. - addBox(); - cam.setLocation(new Vector3f(0f, 1f, 8f)); - - // Add a light to the scene. - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - - // Add a model to the scene. - Spatial ninjaModel - = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); - rootNode.attachChild(ninjaModel); - ninjaModel.rotate(0f, 3f, 0f); - ninjaModel.scale(0.02f); - - // Configure a DynamicAnimControl. - LinkConfig defaultConfig = new LinkConfig(); - RangeOfMotion defaultRom = new RangeOfMotion(1f); - final DynamicAnimControl dac = new DynamicAnimControl(); - dac.link("Joint9", defaultConfig, defaultRom); // right shoulder - dac.link("Joint11", defaultConfig, defaultRom); // right elbow - dac.link("Joint12", defaultConfig, defaultRom); // right wrist - - // NOTE: Complete configuration BEFORE adding control to a model. - ninjaModel.addControl(dac); - dac.setPhysicsSpace(physicsSpace); - - // Configure InputManager to respond to the spacebar. - inputManager.addMapping("go limp", new KeyTrigger(KeyInput.KEY_SPACE)); - InputListener actionListener = new ActionListener() { - @Override - public void onAction(String action, boolean ongoing, float tpf) { - if (action.equals("go limp") && ongoing) { - dac.setRagdollMode(); - } - } - }; - inputManager.addListener(actionListener, "go limp"); - } - // ************************************************************************* - // private methods - - /** - * Add a large static cube to serve as a platform. - */ - private void addBox() { - float halfExtent = 50f; // mesh units - Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); - Geometry geometry = new Geometry("cube platform", mesh); - rootNode.attachChild(geometry); - - geometry.move(0f, -halfExtent, 0f); - ColorRGBA color = new ColorRGBA(0.1f, 0.4f, 0.1f, 1f); - Material material = new Material(assetManager, Materials.LIGHTING); - material.setBoolean("UseMaterialColors", true); - material.setColor("Diffuse", color); - geometry.setMaterial(material); - geometry.setShadowMode(RenderQueue.ShadowMode.Receive); - - BoxCollisionShape shape = new BoxCollisionShape(halfExtent); - RigidBodyControl boxBody - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - geometry.addControl(boxBody); - boxBody.setPhysicsSpace(physicsSpace); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.bullet.animation.LinkConfig; +import com.jme3.bullet.animation.RangeOfMotion; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +/** + * A simple example of a DynamicAnimControl with 3 bone links. + *

+ * Builds upon HelloDac. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloBoneLink extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloBoneLink application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloBoneLink application = new HelloBoneLink(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + physicsSpace = bulletAppState.getPhysicsSpace(); + + // Add a box to the scene and relocate the camera. + addBox(); + cam.setLocation(new Vector3f(0f, 1f, 8f)); + + // Add a light to the scene. + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + + // Add a model to the scene. + Spatial ninjaModel + = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); + rootNode.attachChild(ninjaModel); + ninjaModel.rotate(0f, 3f, 0f); + ninjaModel.scale(0.02f); + + // Configure a DynamicAnimControl. + LinkConfig defaultConfig = new LinkConfig(); + RangeOfMotion defaultRom = new RangeOfMotion(1f); + final DynamicAnimControl dac = new DynamicAnimControl(); + dac.link("Joint9", defaultConfig, defaultRom); // right shoulder + dac.link("Joint11", defaultConfig, defaultRom); // right elbow + dac.link("Joint12", defaultConfig, defaultRom); // right wrist + + // NOTE: Complete configuration BEFORE adding control to a model. + ninjaModel.addControl(dac); + dac.setPhysicsSpace(physicsSpace); + + // Configure InputManager to respond to the spacebar. + inputManager.addMapping("go limp", new KeyTrigger(KeyInput.KEY_SPACE)); + InputListener actionListener = new ActionListener() { + @Override + public void onAction(String action, boolean ongoing, float tpf) { + if (action.equals("go limp") && ongoing) { + dac.setRagdollMode(); + } + } + }; + inputManager.addListener(actionListener, "go limp"); + } + // ************************************************************************* + // private methods + + /** + * Add a large static cube to serve as a platform. + */ + private void addBox() { + float halfExtent = 50f; // mesh units + Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); + Geometry geometry = new Geometry("cube platform", mesh); + rootNode.attachChild(geometry); + + geometry.move(0f, -halfExtent, 0f); + ColorRGBA color = new ColorRGBA(0.1f, 0.4f, 0.1f, 1f); + Material material = new Material(assetManager, Materials.LIGHTING); + material.setBoolean("UseMaterialColors", true); + material.setColor("Diffuse", color); + geometry.setMaterial(material); + geometry.setShadowMode(RenderQueue.ShadowMode.Receive); + + BoxCollisionShape shape = new BoxCollisionShape(halfExtent); + RigidBodyControl boxBody + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + geometry.addControl(boxBody); + boxBody.setPhysicsSpace(physicsSpace); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCcd.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCcd.java index 4a20fbb6e..e4be30d10 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCcd.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCcd.java @@ -1,109 +1,109 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example of continuous collision detection (CCD). - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloCcd extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloCcd application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloCcd application = new HelloCcd(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // For clarity, simulate at 1/10th normal speed. - bulletAppState.setSpeed(0.1f); - - // Increase gravity to make the balls fall faster. - physicsSpace.setGravity(new Vector3f(0f, -100f, 0f)); - - // Create a CollisionShape for balls. - float ballRadius = 0.1f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - - // Create 2 dynamic balls, one with CCD and one without, - // and add them to the space. - float mass = 1f; - PhysicsRigidBody ccdBall = new PhysicsRigidBody(ballShape, mass); - physicsSpace.addCollisionObject(ccdBall); - ccdBall.setCcdMotionThreshold(ballRadius); - ccdBall.setCcdSweptSphereRadius(ballRadius); - ccdBall.setPhysicsLocation(new Vector3f(-1f, 4f, 0f)); - - PhysicsRigidBody controlBall = new PhysicsRigidBody(ballShape, mass); - physicsSpace.addCollisionObject(controlBall); - controlBall.setPhysicsLocation(new Vector3f(1f, 4f, 0f)); - - // Create a thin, static disc and add it to the space. - float discRadius = 2f; - float discThickness = 0.05f; - CollisionShape discShape = new CylinderCollisionShape( - discRadius, discThickness, PhysicsSpace.AXIS_Y); - PhysicsRigidBody disc - = new PhysicsRigidBody(discShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(disc); - - // Minie's BulletAppState simulates the dynamics... - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example of continuous collision detection (CCD). + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloCcd extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloCcd application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloCcd application = new HelloCcd(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // For clarity, simulate at 1/10th normal speed. + bulletAppState.setSpeed(0.1f); + + // Increase gravity to make the balls fall faster. + physicsSpace.setGravity(new Vector3f(0f, -100f, 0f)); + + // Create a CollisionShape for balls. + float ballRadius = 0.1f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + + // Create 2 dynamic balls, one with CCD and one without, + // and add them to the space. + float mass = 1f; + PhysicsRigidBody ccdBall = new PhysicsRigidBody(ballShape, mass); + physicsSpace.addCollisionObject(ccdBall); + ccdBall.setCcdMotionThreshold(ballRadius); + ccdBall.setCcdSweptSphereRadius(ballRadius); + ccdBall.setPhysicsLocation(new Vector3f(-1f, 4f, 0f)); + + PhysicsRigidBody controlBall = new PhysicsRigidBody(ballShape, mass); + physicsSpace.addCollisionObject(controlBall); + controlBall.setPhysicsLocation(new Vector3f(1f, 4f, 0f)); + + // Create a thin, static disc and add it to the space. + float discRadius = 2f; + float discThickness = 0.05f; + CollisionShape discShape = new CylinderCollisionShape( + discRadius, discThickness, PhysicsSpace.AXIS_Y); + PhysicsRigidBody disc + = new PhysicsRigidBody(discShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(disc); + + // Minie's BulletAppState simulates the dynamics... + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacter.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacter.java index ced37f6e5..c0090ee7a 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacter.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacter.java @@ -1,272 +1,272 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.Box2dShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsCharacter; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import jme3utilities.MeshNormals; - -/** - * A simple example of character physics. - *

- * Builds upon HelloCustomDebug. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloCharacter - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - /** - * character being tested - */ - private static PhysicsCharacter character; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloCharacter application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloCharacter application = new HelloCharacter(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - physicsSpace = configurePhysics(); - - // Create a character with a capsule shape and add it to the space. - float capsuleRadius = 0.5f; - float capsuleHeight = 1f; - CapsuleCollisionShape shape - = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); - float stepHeight = 0.01f; - character = new PhysicsCharacter(shape, stepHeight); - physicsSpace.addCollisionObject(character); - - // Add a square to represent the ground. - float halfExtent = 4f; - float y = -2f; - PhysicsRigidBody ground = addSquare(halfExtent, y); - - // Customize the debug visualization of each object. - Material redMaterial = createLitMaterial(1f, 0f, 0f); - character.setDebugMaterial(redMaterial); - character.setDebugMeshNormals(MeshNormals.Smooth); - character.setDebugMeshResolution(DebugShapeFactory.highResolution); - - Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); - ground.setDebugMaterial(greenMaterial); - ground.setDebugMeshNormals(MeshNormals.Facet); - - // Minie's BulletAppState simulates the dynamics... - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // If the character is touching the ground, cause it to jump. - if (character.onGround()) { - character.jump(); - } - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.4f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Add a horizontal square body to the space. - * - * @param halfExtent (half of the desired side length) - * @param y (the desired elevation, in physics-space coordinates) - * @return the new body (not null) - */ - private PhysicsRigidBody addSquare(float halfExtent, float y) { - // Construct a static rigid body with a square shape. - Box2dShape shape = new Box2dShape(halfExtent); - PhysicsRigidBody result - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - physicsSpace.addCollisionObject(result); - - // Rotate it 90 degrees to a horizontal orientation. - Quaternion rotate90 - = new Quaternion().fromAngles(-FastMath.HALF_PI, 0f, 0f); - result.setPhysicsRotation(rotate90); - - // Translate it to the desired elevation. - result.setPhysicsLocation(new Vector3f(0f, y, 0f)); - - return result; - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.Box2dShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import jme3utilities.MeshNormals; + +/** + * A simple example of character physics. + *

+ * Builds upon HelloCustomDebug. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloCharacter + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + /** + * character being tested + */ + private static PhysicsCharacter character; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloCharacter application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloCharacter application = new HelloCharacter(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + physicsSpace = configurePhysics(); + + // Create a character with a capsule shape and add it to the space. + float capsuleRadius = 0.5f; + float capsuleHeight = 1f; + CapsuleCollisionShape shape + = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); + float stepHeight = 0.01f; + character = new PhysicsCharacter(shape, stepHeight); + physicsSpace.addCollisionObject(character); + + // Add a square to represent the ground. + float halfExtent = 4f; + float y = -2f; + PhysicsRigidBody ground = addSquare(halfExtent, y); + + // Customize the debug visualization of each object. + Material redMaterial = createLitMaterial(1f, 0f, 0f); + character.setDebugMaterial(redMaterial); + character.setDebugMeshNormals(MeshNormals.Smooth); + character.setDebugMeshResolution(DebugShapeFactory.highResolution); + + Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); + ground.setDebugMaterial(greenMaterial); + ground.setDebugMeshNormals(MeshNormals.Facet); + + // Minie's BulletAppState simulates the dynamics... + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // If the character is touching the ground, cause it to jump. + if (character.onGround()) { + character.jump(); + } + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.4f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Add a horizontal square body to the space. + * + * @param halfExtent (half of the desired side length) + * @param y (the desired elevation, in physics-space coordinates) + * @return the new body (not null) + */ + private PhysicsRigidBody addSquare(float halfExtent, float y) { + // Construct a static rigid body with a square shape. + Box2dShape shape = new Box2dShape(halfExtent); + PhysicsRigidBody result + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + physicsSpace.addCollisionObject(result); + + // Rotate it 90 degrees to a horizontal orientation. + Quaternion rotate90 + = new Quaternion().fromAngles(-FastMath.HALF_PI, 0f, 0f); + result.setPhysicsRotation(rotate90); + + // Translate it to the desired elevation. + result.setPhysicsLocation(new Vector3f(0f, y, 0f)); + + return result; + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacterControl.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacterControl.java index c15b06a41..7e6106b1e 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacterControl.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCharacterControl.java @@ -1,266 +1,266 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.control.CharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; - -/** - * A simple example of a CharacterControl. - *

- * Builds upon HelloCharacter. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloCharacterControl - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - private static CharacterControl characterControl; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloCharacterControl application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloCharacterControl application = new HelloCharacterControl(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - physicsSpace = configurePhysics(); - - // Load the Jaime model from jme3-testdata-3.1.0-stable.jar - Spatial jaime = assetManager.loadModel("Models/Jaime/Jaime.j3o"); - jaime.setShadowMode(RenderQueue.ShadowMode.Cast); - jaime.move(0f, -1f, 0f); - jaime.scale(1.4f); - - // Attach Jaime to a new scene-graph node located near its center. - Node centerNode = new Node("center node"); - centerNode.attachChild(jaime); - - // Attach the center node to the scene. - rootNode.attachChild(centerNode); - - // Create a CharacterControl with a capsule shape. - float capsuleRadius = 0.5f; - float capsuleHeight = 1f; - CapsuleCollisionShape shape - = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); - float stepHeight = 0.01f; - characterControl = new CharacterControl(shape, stepHeight); - - // Add the control to the center node and the PhysicsSpace. - centerNode.addControl(characterControl); - characterControl.setPhysicsSpace(physicsSpace); - - // Add a square to represent the ground. - float halfExtent = 4f; - float y = -2f; - addSquare(halfExtent, y); - - // Add lighting. - addLighting(rootNode); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // If the character is touching the ground, cause it to jump. - if (characterControl.onGround()) { - characterControl.jump(); - } - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.4f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Attach a horizontal square to the scene and also to the space. - * - * @param halfExtent (half of the desired side length) - * @param y (the desired elevation, in physics-space coordinates) - * @return the new body (not null) - */ - private RigidBodyControl addSquare(float halfExtent, float y) { - // Add a Quad to the scene. - Mesh quad = new Quad(2 * halfExtent, 2 * halfExtent); - Geometry geometry = new Geometry("square", quad); - Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); - geometry.setMaterial(greenMaterial); - geometry.setShadowMode(RenderQueue.ShadowMode.Receive); - rootNode.attachChild(geometry); - - // Rotate it 90 degrees to a horizontal orientation. - geometry.rotate(-FastMath.HALF_PI, 0f, 0f); - - // Translate it to the desired elevation. - geometry.move(-halfExtent, y, halfExtent); - - // Add a static RBC to the Geometry, to make it solid. - RigidBodyControl result - = new RigidBodyControl(PhysicsBody.massForStatic); - geometry.addControl(result); - physicsSpace.addCollisionObject(result); - - return result; - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; + +/** + * A simple example of a CharacterControl. + *

+ * Builds upon HelloCharacter. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloCharacterControl + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + private static CharacterControl characterControl; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloCharacterControl application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloCharacterControl application = new HelloCharacterControl(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + physicsSpace = configurePhysics(); + + // Load the Jaime model from jme3-testdata-3.1.0-stable.jar + Spatial jaime = assetManager.loadModel("Models/Jaime/Jaime.j3o"); + jaime.setShadowMode(RenderQueue.ShadowMode.Cast); + jaime.move(0f, -1f, 0f); + jaime.scale(1.4f); + + // Attach Jaime to a new scene-graph node located near its center. + Node centerNode = new Node("center node"); + centerNode.attachChild(jaime); + + // Attach the center node to the scene. + rootNode.attachChild(centerNode); + + // Create a CharacterControl with a capsule shape. + float capsuleRadius = 0.5f; + float capsuleHeight = 1f; + CapsuleCollisionShape shape + = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); + float stepHeight = 0.01f; + characterControl = new CharacterControl(shape, stepHeight); + + // Add the control to the center node and the PhysicsSpace. + centerNode.addControl(characterControl); + characterControl.setPhysicsSpace(physicsSpace); + + // Add a square to represent the ground. + float halfExtent = 4f; + float y = -2f; + addSquare(halfExtent, y); + + // Add lighting. + addLighting(rootNode); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // If the character is touching the ground, cause it to jump. + if (characterControl.onGround()) { + characterControl.jump(); + } + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.4f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Attach a horizontal square to the scene and also to the space. + * + * @param halfExtent (half of the desired side length) + * @param y (the desired elevation, in physics-space coordinates) + * @return the new body (not null) + */ + private RigidBodyControl addSquare(float halfExtent, float y) { + // Add a Quad to the scene. + Mesh quad = new Quad(2 * halfExtent, 2 * halfExtent); + Geometry geometry = new Geometry("square", quad); + Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); + geometry.setMaterial(greenMaterial); + geometry.setShadowMode(RenderQueue.ShadowMode.Receive); + rootNode.attachChild(geometry); + + // Rotate it 90 degrees to a horizontal orientation. + geometry.rotate(-FastMath.HALF_PI, 0f, 0f); + + // Translate it to the desired elevation. + geometry.move(-halfExtent, y, halfExtent); + + // Add a static RBC to the Geometry, to make it solid. + RigidBodyControl result + = new RigidBodyControl(PhysicsBody.massForStatic); + geometry.addControl(result); + physicsSpace.addCollisionObject(result); + + return result; + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCloth.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCloth.java index d307b2835..c48902393 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCloth.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCloth.java @@ -1,111 +1,111 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import jme3utilities.mesh.ClothGrid; - -/** - * A simple cloth simulation using a soft body. - *

- * Builds upon HelloSoftBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloCloth extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloCloth application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloCloth application = new HelloCloth(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - // Relocate the camera. - cam.setLocation(new Vector3f(0f, 1f, 8f)); - - // Create a static, rigid sphere and add it to the physics space. - float radius = 1f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - PhysicsRigidBody sphere - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(sphere); - - // Generate a subdivided square mesh with alternating diagonals. - int numLines = 41; - float lineSpacing = 0.1f; // mesh units - Mesh squareGrid = new ClothGrid(numLines, numLines, lineSpacing); - - // Create a soft square and add it to the physics space. - PhysicsSoftBody cloth = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(squareGrid, cloth); - physicsSpace.addCollisionObject(cloth); - - // Make the cloth flexible by altering the angular stiffness - // of its material. - SoftBodyMaterial mat = cloth.getSoftMaterial(); - mat.setAngularStiffness(0f); // default=1 - /* - * Improve simulation accuracy by increasing - * the number of position-solver iterations for the cloth. - */ - SoftBodyConfig config = cloth.getSoftConfig(); - config.setPositionIterations(9); // default=1 - - // Translate the cloth upward to its starting location. - cloth.applyTranslation(new Vector3f(0f, 2f, 0f)); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import jme3utilities.mesh.ClothGrid; + +/** + * A simple cloth simulation using a soft body. + *

+ * Builds upon HelloSoftBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloCloth extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloCloth application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloCloth application = new HelloCloth(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + // Relocate the camera. + cam.setLocation(new Vector3f(0f, 1f, 8f)); + + // Create a static, rigid sphere and add it to the physics space. + float radius = 1f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + PhysicsRigidBody sphere + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(sphere); + + // Generate a subdivided square mesh with alternating diagonals. + int numLines = 41; + float lineSpacing = 0.1f; // mesh units + Mesh squareGrid = new ClothGrid(numLines, numLines, lineSpacing); + + // Create a soft square and add it to the physics space. + PhysicsSoftBody cloth = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(squareGrid, cloth); + physicsSpace.addCollisionObject(cloth); + + // Make the cloth flexible by altering the angular stiffness + // of its material. + SoftBodyMaterial mat = cloth.getSoftMaterial(); + mat.setAngularStiffness(0f); // default=1 + /* + * Improve simulation accuracy by increasing + * the number of position-solver iterations for the cloth. + */ + SoftBodyConfig config = cloth.getSoftConfig(); + config.setPositionIterations(9); // default=1 + + // Translate the cloth upward to its starting location. + cloth.applyTranslation(new Vector3f(0f, 2f, 0f)); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloContactResponse.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloContactResponse.java index 1da1a5781..8e47cc2e5 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloContactResponse.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloContactResponse.java @@ -1,116 +1,116 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.InputListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.math.Vector3f; - -/** - * A simple demonstration of contact response. - *

- * Press the spacebar to disable the ball's contact response. Once this happens, - * the blue (static) box no longer exerts any contact force on the ball. Gravity - * takes over, and the ball falls through. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloContactResponse extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloContactResponse application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloContactResponse application = new HelloContactResponse(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Add a static box to the space, to serve as a horizontal platform. - float boxHalfExtent = 3f; - CollisionShape boxShape = new BoxCollisionShape(boxHalfExtent); - PhysicsRigidBody box - = new PhysicsRigidBody(boxShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(box); - box.setPhysicsLocation(new Vector3f(0f, -4f, 0f)); - - // Add a dynamic ball to the space. - float ballRadius = 1f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - float ballMass = 2f; - final PhysicsRigidBody ball = new PhysicsRigidBody(ballShape, ballMass); - physicsSpace.addCollisionObject(ball); - assert ball.isContactResponse(); - - // Position the ball directly above the box. - ball.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Configure the InputManager to respond to the spacebar. - inputManager.addMapping("freefall", new KeyTrigger(KeyInput.KEY_SPACE)); - InputListener actionListener = new ActionListener() { - @Override - public void onAction(String action, boolean ongoing, float tpf) { - if (action.equals("freefall") && ongoing) { - // Disable the ball's contact response. - ball.setContactResponse(false); - - // Activate the ball in case it got deactivated. - ball.activate(); - } - } - }; - inputManager.addListener(actionListener, "freefall"); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.Vector3f; + +/** + * A simple demonstration of contact response. + *

+ * Press the spacebar to disable the ball's contact response. Once this happens, + * the blue (static) box no longer exerts any contact force on the ball. Gravity + * takes over, and the ball falls through. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloContactResponse extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloContactResponse application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloContactResponse application = new HelloContactResponse(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Add a static box to the space, to serve as a horizontal platform. + float boxHalfExtent = 3f; + CollisionShape boxShape = new BoxCollisionShape(boxHalfExtent); + PhysicsRigidBody box + = new PhysicsRigidBody(boxShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(box); + box.setPhysicsLocation(new Vector3f(0f, -4f, 0f)); + + // Add a dynamic ball to the space. + float ballRadius = 1f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + float ballMass = 2f; + final PhysicsRigidBody ball = new PhysicsRigidBody(ballShape, ballMass); + physicsSpace.addCollisionObject(ball); + assert ball.isContactResponse(); + + // Position the ball directly above the box. + ball.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Configure the InputManager to respond to the spacebar. + inputManager.addMapping("freefall", new KeyTrigger(KeyInput.KEY_SPACE)); + InputListener actionListener = new ActionListener() { + @Override + public void onAction(String action, boolean ongoing, float tpf) { + if (action.equals("freefall") && ongoing) { + // Disable the ball's contact response. + ball.setContactResponse(false); + + // Activate the ball in case it got deactivated. + ball.activate(); + } + } + }; + inputManager.addListener(actionListener, "freefall"); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCustomDebug.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCustomDebug.java index 5f5996bf3..ffb11cd7a 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCustomDebug.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloCustomDebug.java @@ -1,163 +1,163 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.DebugShapeFactory; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import jme3utilities.MeshNormals; - -/** - * A simple example that demonstrates customization of debug materials, debug - * meshes, and lighting. - *

- * Builds upon HelloRbc. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloCustomDebug extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloCustomDebug application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloCustomDebug application = new HelloCustomDebug(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - PhysicsSpace physicsSpace = configurePhysics(); - - // Create a material and CollisionShape for balls. - Material ballMaterial = new Material(assetManager, Materials.LIGHTING); - float ballRadius = 1f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - - // Create rigid bodies for a dynamic ball and a static ball. - float mass = 2f; - PhysicsRigidBody dynaBall = new PhysicsRigidBody(ballShape, mass); - - PhysicsRigidBody statBall - = new PhysicsRigidBody(ballShape, PhysicsBody.massForStatic); - - // Add the bodies to the physics space. - physicsSpace.addCollisionObject(dynaBall); - physicsSpace.addCollisionObject(statBall); - - // Position the balls in physics space. - dynaBall.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - statBall.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); - - // Customize the debug visualization of each object. - dynaBall.setDebugMaterial(ballMaterial); - dynaBall.setDebugMeshNormals(MeshNormals.Sphere); - dynaBall.setDebugMeshResolution(DebugShapeFactory.highResolution); - - statBall.setDebugMaterial(ballMaterial); - statBall.setDebugMeshNormals(MeshNormals.Sphere); - statBall.setDebugMeshResolution(DebugShapeFactory.highResolution); - - // Minie's BulletAppState simulates the dynamics... - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private static void addLighting(Spatial scene) { - // Light the scene with ambient and directional lights. - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - - // Add lighting to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.DebugShapeFactory; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import jme3utilities.MeshNormals; + +/** + * A simple example that demonstrates customization of debug materials, debug + * meshes, and lighting. + *

+ * Builds upon HelloRbc. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloCustomDebug extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloCustomDebug application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloCustomDebug application = new HelloCustomDebug(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + PhysicsSpace physicsSpace = configurePhysics(); + + // Create a material and CollisionShape for balls. + Material ballMaterial = new Material(assetManager, Materials.LIGHTING); + float ballRadius = 1f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + + // Create rigid bodies for a dynamic ball and a static ball. + float mass = 2f; + PhysicsRigidBody dynaBall = new PhysicsRigidBody(ballShape, mass); + + PhysicsRigidBody statBall + = new PhysicsRigidBody(ballShape, PhysicsBody.massForStatic); + + // Add the bodies to the physics space. + physicsSpace.addCollisionObject(dynaBall); + physicsSpace.addCollisionObject(statBall); + + // Position the balls in physics space. + dynaBall.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + statBall.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); + + // Customize the debug visualization of each object. + dynaBall.setDebugMaterial(ballMaterial); + dynaBall.setDebugMeshNormals(MeshNormals.Sphere); + dynaBall.setDebugMeshResolution(DebugShapeFactory.highResolution); + + statBall.setDebugMaterial(ballMaterial); + statBall.setDebugMeshNormals(MeshNormals.Sphere); + statBall.setDebugMeshResolution(DebugShapeFactory.highResolution); + + // Minie's BulletAppState simulates the dynamics... + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private static void addLighting(Spatial scene) { + // Light the scene with ambient and directional lights. + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + + // Add lighting to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDac.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDac.java index 70ba23e0a..1c593c614 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDac.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDac.java @@ -1,103 +1,103 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.animation.DynamicAnimControl; -import com.jme3.light.DirectionalLight; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import java.util.List; -import jme3utilities.MySpatial; - -/** - * A very simple example using DynamicAnimControl. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloDac extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloDac application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloDac application = new HelloDac(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Add a light to the scene. - Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - - // Add a model to the scene. - Spatial ninjaModel - = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); - rootNode.attachChild(ninjaModel); - ninjaModel.rotate(0f, 3f, 0f); - ninjaModel.scale(0.02f); - - // The DynamicAnimControl must be added to the Spatial controlled by - // the model's SkinningControl (or SkeletonControl). - // MySpatial.listAnimationSpatials() is used to locate that Spatial. - List list = MySpatial.listAnimationSpatials(ninjaModel, null); - assert list.size() == 1 : list.size(); - Spatial controlled = list.get(0); - - // In the Ninja model, that Spatial is the model's root Node. - assert controlled == ninjaModel; - - // Add a DynamicAnimControl to the model. - DynamicAnimControl dac = new DynamicAnimControl(); - controlled.addControl(dac); - - dac.setPhysicsSpace(physicsSpace); - - // Because no bone links are configured, the model would behave more - // like a rigid body than a ragdoll. See HelloBoneLink for an - // example of configuring bone links. - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.animation.DynamicAnimControl; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import java.util.List; +import jme3utilities.MySpatial; + +/** + * A very simple example using DynamicAnimControl. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloDac extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloDac application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloDac application = new HelloDac(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Add a light to the scene. + Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + + // Add a model to the scene. + Spatial ninjaModel + = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); + rootNode.attachChild(ninjaModel); + ninjaModel.rotate(0f, 3f, 0f); + ninjaModel.scale(0.02f); + + // The DynamicAnimControl must be added to the Spatial controlled by + // the model's SkinningControl (or SkeletonControl). + // MySpatial.listAnimationSpatials() is used to locate that Spatial. + List list = MySpatial.listAnimationSpatials(ninjaModel, null); + assert list.size() == 1 : list.size(); + Spatial controlled = list.get(0); + + // In the Ninja model, that Spatial is the model's root Node. + assert controlled == ninjaModel; + + // Add a DynamicAnimControl to the model. + DynamicAnimControl dac = new DynamicAnimControl(); + controlled.addControl(dac); + + dac.setPhysicsSpace(physicsSpace); + + // Because no bone links are configured, the model would behave more + // like a rigid body than a ragdoll. See HelloBoneLink for an + // example of configuring bone links. + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDamping.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDamping.java index 5452a582a..4c64bae31 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDamping.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDamping.java @@ -1,114 +1,114 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example illustrating the effect of damping on dynamic rigid bodies. - *

- * Builds upon HelloRigidBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloDamping extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloDamping application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloDamping application = new HelloDamping(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // For clarity, disable gravity. - physicsSpace.setGravity(Vector3f.ZERO); - - // Create a CollisionShape for unit cubes. - float cubeHalfExtent = 0.5f; - CollisionShape cubeShape = new BoxCollisionShape(cubeHalfExtent); - - // Create 4 cubes (dynamic rigid bodies) and add them to the space. - int numCubes = 4; - float cubeMass = 2f; - PhysicsRigidBody[] cube = new PhysicsRigidBody[numCubes]; - for (int cubeIndex = 0; cubeIndex < numCubes; ++cubeIndex) { - cube[cubeIndex] = new PhysicsRigidBody(cubeShape, cubeMass); - physicsSpace.addCollisionObject(cube[cubeIndex]); - - // Disable sleep (deactivation) for clarity. - cube[cubeIndex].setEnableSleep(false); - } - - // Locate the cubes 4 psu apart, center to center. - cube[0].setPhysicsLocation(new Vector3f(0f, +2f, 0f)); - cube[1].setPhysicsLocation(new Vector3f(4f, +2f, 0f)); - cube[2].setPhysicsLocation(new Vector3f(0f, -2f, 0f)); - cube[3].setPhysicsLocation(new Vector3f(4f, -2f, 0f)); - - // Give each cube its own set of damping parameters (linear, angular). - cube[0].setDamping(0f, 0f); - cube[1].setDamping(0f, 0.9f); - cube[2].setDamping(0.9f, 0f); - cube[3].setDamping(0.9f, 0.9f); - - // Apply an off-center impulse to each cube, - // causing it to drift and spin. - Vector3f impulse = new Vector3f(-1f, 0f, 0f); - Vector3f offset = new Vector3f(0f, 1f, 1f); - for (int cubeIndex = 0; cubeIndex < numCubes; ++cubeIndex) { - cube[cubeIndex].applyImpulse(impulse, offset); - } - - // Minie's BulletAppState simulates the dynamics... - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example illustrating the effect of damping on dynamic rigid bodies. + *

+ * Builds upon HelloRigidBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloDamping extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloDamping application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloDamping application = new HelloDamping(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // For clarity, disable gravity. + physicsSpace.setGravity(Vector3f.ZERO); + + // Create a CollisionShape for unit cubes. + float cubeHalfExtent = 0.5f; + CollisionShape cubeShape = new BoxCollisionShape(cubeHalfExtent); + + // Create 4 cubes (dynamic rigid bodies) and add them to the space. + int numCubes = 4; + float cubeMass = 2f; + PhysicsRigidBody[] cube = new PhysicsRigidBody[numCubes]; + for (int cubeIndex = 0; cubeIndex < numCubes; ++cubeIndex) { + cube[cubeIndex] = new PhysicsRigidBody(cubeShape, cubeMass); + physicsSpace.addCollisionObject(cube[cubeIndex]); + + // Disable sleep (deactivation) for clarity. + cube[cubeIndex].setEnableSleep(false); + } + + // Locate the cubes 4 psu apart, center to center. + cube[0].setPhysicsLocation(new Vector3f(0f, +2f, 0f)); + cube[1].setPhysicsLocation(new Vector3f(4f, +2f, 0f)); + cube[2].setPhysicsLocation(new Vector3f(0f, -2f, 0f)); + cube[3].setPhysicsLocation(new Vector3f(4f, -2f, 0f)); + + // Give each cube its own set of damping parameters (linear, angular). + cube[0].setDamping(0f, 0f); + cube[1].setDamping(0f, 0.9f); + cube[2].setDamping(0.9f, 0f); + cube[3].setDamping(0.9f, 0.9f); + + // Apply an off-center impulse to each cube, + // causing it to drift and spin. + Vector3f impulse = new Vector3f(-1f, 0f, 0f); + Vector3f offset = new Vector3f(0f, 1f, 1f); + for (int cubeIndex = 0; cubeIndex < numCubes; ++cubeIndex) { + cube[cubeIndex].applyImpulse(impulse, offset); + } + + // Minie's BulletAppState simulates the dynamics... + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDeactivation.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDeactivation.java index 1b342984b..e504eff14 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDeactivation.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDeactivation.java @@ -1,142 +1,142 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example of rigid-body deactivation. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloDeactivation - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - private static PhysicsRigidBody dynamicCube; - private static PhysicsRigidBody supportCube; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloDeactivation application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloDeactivation application = new HelloDeactivation(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - physicsSpace.addTickListener(this); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Create a dynamic cube and add it to the space. - float boxHalfExtent = 0.5f; - CollisionShape smallCubeShape = new BoxCollisionShape(boxHalfExtent); - float boxMass = 1f; - dynamicCube = new PhysicsRigidBody(smallCubeShape, boxMass); - physicsSpace.addCollisionObject(dynamicCube); - dynamicCube.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Create 2 static bodies and add them to the space... - // The top body serves as a temporary support. - float cubeHalfExtent = 1f; - CollisionShape largeCubeShape = new BoxCollisionShape(cubeHalfExtent); - supportCube = new PhysicsRigidBody( - largeCubeShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(supportCube); - - // The bottom body serves as a visual reference point. - float ballRadius = 0.5f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - PhysicsRigidBody bottomBody = new PhysicsRigidBody( - ballShape, PhysicsBody.massForStatic); - bottomBody.setPhysicsLocation(new Vector3f(0f, -2f, 0f)); - physicsSpace.addCollisionObject(bottomBody); - - // Minie's BulletAppState simulates the dynamics... - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - /* - * Once the dynamic cube gets deactivated, - * remove the support cube from the PhysicsSpace. - */ - if (!dynamicCube.isActive() && space.contains(supportCube)) { - space.removeCollisionObject(supportCube); - } - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example of rigid-body deactivation. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloDeactivation + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + private static PhysicsRigidBody dynamicCube; + private static PhysicsRigidBody supportCube; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloDeactivation application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloDeactivation application = new HelloDeactivation(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + physicsSpace.addTickListener(this); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Create a dynamic cube and add it to the space. + float boxHalfExtent = 0.5f; + CollisionShape smallCubeShape = new BoxCollisionShape(boxHalfExtent); + float boxMass = 1f; + dynamicCube = new PhysicsRigidBody(smallCubeShape, boxMass); + physicsSpace.addCollisionObject(dynamicCube); + dynamicCube.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Create 2 static bodies and add them to the space... + // The top body serves as a temporary support. + float cubeHalfExtent = 1f; + CollisionShape largeCubeShape = new BoxCollisionShape(cubeHalfExtent); + supportCube = new PhysicsRigidBody( + largeCubeShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(supportCube); + + // The bottom body serves as a visual reference point. + float ballRadius = 0.5f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + PhysicsRigidBody bottomBody = new PhysicsRigidBody( + ballShape, PhysicsBody.massForStatic); + bottomBody.setPhysicsLocation(new Vector3f(0f, -2f, 0f)); + physicsSpace.addCollisionObject(bottomBody); + + // Minie's BulletAppState simulates the dynamics... + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + /* + * Once the dynamic cube gets deactivated, + * remove the support cube from the PhysicsSpace. + */ + if (!dynamicCube.isActive() && space.contains(supportCube)) { + space.removeCollisionObject(supportCube); + } + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDebugToPost.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDebugToPost.java index 70e8226a3..882b0588c 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDebugToPost.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDebugToPost.java @@ -1,130 +1,130 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.ViewPort; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.shape.Box; - -/** - * A simple example of debug visualization in a post ViewPort. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloDebugToPost extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloDebugToPost application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - Application application = new HelloDebugToPost(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - - // Set up Bullet physics (with debug enabled). - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - physicsSpace = bulletAppState.getPhysicsSpace(); - /* - * Direct debug visuals to a post ViewPort that clears the depth buffer. - * This prevents z-fighting between the box and its debug visuals, - * but allows debug visuals to overdraw the GUI. - */ - ViewPort overlay = renderManager.createPostView("Overlay", cam); - overlay.setClearFlags(false, true, false); - bulletAppState.setDebugViewPorts(overlay); - - addBox(); - } - // ************************************************************************* - // private methods - - /** - * Add a large static cube. - */ - private void addBox() { - float halfExtent = 1f; // mesh units - Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); - Geometry geometry = new Geometry("box", mesh); - rootNode.attachChild(geometry); - - Material boxMaterial = new Material(assetManager, Materials.UNSHADED); - boxMaterial.setColor("Color", ColorRGBA.Gray.clone()); - geometry.setMaterial(boxMaterial); - - BoxCollisionShape shape = new BoxCollisionShape(halfExtent); - RigidBodyControl boxBody - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - geometry.addControl(boxBody); - boxBody.setPhysicsSpace(physicsSpace); - } - - /** - * Configure the camera during startup. - */ - private void configureCamera() { - flyCam.setMoveSpeed(10f); - flyCam.setZoomSpeed(10f); - - cam.setLocation(new Vector3f(4.4f, 4.8f, 14.8f)); - cam.setRotation(new Quaternion(-0.0152f, 0.98352f, -0.15f, -0.09974f)); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.Box; + +/** + * A simple example of debug visualization in a post ViewPort. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloDebugToPost extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloDebugToPost application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + Application application = new HelloDebugToPost(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + + // Set up Bullet physics (with debug enabled). + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + physicsSpace = bulletAppState.getPhysicsSpace(); + /* + * Direct debug visuals to a post ViewPort that clears the depth buffer. + * This prevents z-fighting between the box and its debug visuals, + * but allows debug visuals to overdraw the GUI. + */ + ViewPort overlay = renderManager.createPostView("Overlay", cam); + overlay.setClearFlags(false, true, false); + bulletAppState.setDebugViewPorts(overlay); + + addBox(); + } + // ************************************************************************* + // private methods + + /** + * Add a large static cube. + */ + private void addBox() { + float halfExtent = 1f; // mesh units + Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); + Geometry geometry = new Geometry("box", mesh); + rootNode.attachChild(geometry); + + Material boxMaterial = new Material(assetManager, Materials.UNSHADED); + boxMaterial.setColor("Color", ColorRGBA.Gray.clone()); + geometry.setMaterial(boxMaterial); + + BoxCollisionShape shape = new BoxCollisionShape(halfExtent); + RigidBodyControl boxBody + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + geometry.addControl(boxBody); + boxBody.setPhysicsSpace(physicsSpace); + } + + /** + * Configure the camera during startup. + */ + private void configureCamera() { + flyCam.setMoveSpeed(10f); + flyCam.setZoomSpeed(10f); + + cam.setLocation(new Vector3f(4.4f, 4.8f, 14.8f)); + cam.setRotation(new Quaternion(-0.0152f, 0.98352f, -0.15f, -0.09974f)); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoor.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoor.java index 14e4fb395..7463f9a6f 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoor.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoor.java @@ -1,422 +1,422 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.HingeJoint; -import com.jme3.bullet.joints.JointEnd; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.texture.Texture; -import jme3utilities.MeshNormals; - -/** - * Simulate a swinging door using a single-ended HingeJoint. - *

- * Builds upon HelloJoint. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloDoor - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * half the height of the door (in physics-space units) - */ - final private static float doorHalfHeight = 4f; - /** - * half the width of the door (in physics-space units) - */ - final private static float doorHalfWidth = 2f; - /** - * physics-space Y coordinate of the ground plane - */ - final private static float groundY = -4f; - /** - * half the thickness of the door and door frame (in physics-space units) - */ - final private static float halfThickness = 0.3f; - // ************************************************************************* - // fields - - /** - * mouse-controlled kinematic ball - */ - private static PhysicsRigidBody ballBody; - /** - * dynamic swinging door - */ - private static PhysicsRigidBody doorBody; - /** - * static door frame - */ - private static PhysicsRigidBody doorFrameBody; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - /** - * latest ground location indicated by the mouse cursor - */ - final private static Vector3f mouseLocation = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloDoor application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloDoor application = new HelloDoor(); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - - // Enable gamma correction for accurate lighting. - settings.setGammaCorrection(true); - - // Disable VSync for more frequent mouse-position updates. - settings.setVSync(false); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a static plane to represent the floor. - addPlane(groundY); - - // Add a static body for the door frame. - addDoorFrame(); - - // Add a dynamic body for the door. - addDoor(); - - // Add a single-ended physics joint to constrain the door's motion. - Vector3f pivotInDoor = new Vector3f(-doorHalfWidth, 0f, 0f); - Vector3f pivotInWorld = new Vector3f(-doorHalfWidth, 0f, 0f); - HingeJoint joint = new HingeJoint(doorBody, pivotInDoor, pivotInWorld, - Vector3f.UNIT_Y, Vector3f.UNIT_Y, JointEnd.B); - float lowLimitAngle = -2f; - float highLimitAngle = 2f; - joint.setLimit(lowLimitAngle, highLimitAngle); - physicsSpace.addJoint(joint); - - // Disable collisions between the door and the door frame. - doorBody.addToIgnoreList(doorFrameBody); - - // Add a kinematic, yellow ball. - ballBody = addBall(); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Calculate the ground location (if any) selected by the mouse cursor. - Vector2f screenXy = inputManager.getCursorPosition(); - float nearZ = 0f; - Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); - float farZ = 1f; - Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); - if (nearLocation.y > groundY && farLocation.y < groundY) { - float dy = nearLocation.y - farLocation.y; - float t = (nearLocation.y - groundY) / dy; - FastMath.interpolateLinear( - t, nearLocation, farLocation, mouseLocation); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Reposition the ball based on the mouse location. - Vector3f bodyLocation = mouseLocation.add(0f, doorHalfHeight, 0f); - ballBody.setPhysicsLocation(bodyLocation); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Create a kinematic rigid body with a sphere shape and add it to the - * space. - * - * @return the new body - */ - private PhysicsRigidBody addBall() { - float radius = 0.4f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - result.setKinematic(true); - physicsSpace.addCollisionObject(result); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - - return result; - } - - /** - * Create a dynamic body with a box shape and add it to the space. - */ - private void addDoor() { - BoxCollisionShape shape = new BoxCollisionShape( - doorHalfWidth, doorHalfHeight, halfThickness); - float mass = 1f; - doorBody = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(doorBody); - - // Disable sleep (deactivation). - doorBody.setEnableSleep(false); - - Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); - doorBody.setDebugMaterial(redMaterial); - doorBody.setDebugMeshNormals(MeshNormals.Facet); - } - - /** - * Add a static door frame to the space. - */ - private void addDoorFrame() { - float frameHalfWidth = 0.5f; - BoxCollisionShape jambShape = new BoxCollisionShape( - frameHalfWidth, doorHalfHeight, halfThickness); - - float lintelLength = doorHalfWidth + 2 * frameHalfWidth; - BoxCollisionShape lintelShape = new BoxCollisionShape( - lintelLength, frameHalfWidth, halfThickness); - - CompoundCollisionShape shape = new CompoundCollisionShape(); - shape.addChildShape(jambShape, doorHalfWidth + frameHalfWidth, 0f, 0f); - shape.addChildShape(jambShape, -doorHalfWidth - frameHalfWidth, 0f, 0f); - shape.addChildShape( - lintelShape, 0f, doorHalfHeight + frameHalfWidth, 0f); - - doorFrameBody = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - Material grayMaterial = createLitMaterial(0.5f, 0.5f, 0.5f); - doorFrameBody.setDebugMaterial(grayMaterial); - doorFrameBody.setDebugMeshNormals(MeshNormals.Facet); - - physicsSpace.addCollisionObject(doorFrameBody); - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - } - - /** - * Add a horizontal plane body to the space. - * - * @param y (the desired elevation, in physics-space coordinates) - */ - private void addPlane(float y) { - Plane plane = new Plane(Vector3f.UNIT_Y, y); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody floorBody - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - // Load a repeating tile texture. - String assetPath = "Textures/greenTile.png"; - boolean flipY = false; - TextureKey key = new TextureKey(assetPath, flipY); - boolean generateMips = true; - key.setGenerateMips(generateMips); - Texture texture = assetManager.loadTexture(key); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - - // Enable anisotropic filtering, to reduce blurring. - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - texture.setAnisotropicFilter(degree); - - // Apply a tiled, unshaded debug material to the body. - Material material = new Material(assetManager, Materials.UNSHADED); - material.setTexture("ColorMap", texture); - floorBody.setDebugMaterial(material); - - // Generate texture coordinates during debug-mesh initialization. - float tileSize = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); - floorBody.setDebugMeshInitListener(planeDmiListener); - - physicsSpace.addCollisionObject(floorBody); - } - - /** - * Disable FlyByCamera during startup. - */ - private void configureCamera() { - flyCam.setEnabled(false); - - cam.setLocation(new Vector3f(0f, 12f, 10f)); - cam.setRotation(new Quaternion(0f, 0.9f, -0.43589f, 0f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.HingeJoint; +import com.jme3.bullet.joints.JointEnd; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import jme3utilities.MeshNormals; + +/** + * Simulate a swinging door using a single-ended HingeJoint. + *

+ * Builds upon HelloJoint. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloDoor + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * half the height of the door (in physics-space units) + */ + final private static float doorHalfHeight = 4f; + /** + * half the width of the door (in physics-space units) + */ + final private static float doorHalfWidth = 2f; + /** + * physics-space Y coordinate of the ground plane + */ + final private static float groundY = -4f; + /** + * half the thickness of the door and door frame (in physics-space units) + */ + final private static float halfThickness = 0.3f; + // ************************************************************************* + // fields + + /** + * mouse-controlled kinematic ball + */ + private static PhysicsRigidBody ballBody; + /** + * dynamic swinging door + */ + private static PhysicsRigidBody doorBody; + /** + * static door frame + */ + private static PhysicsRigidBody doorFrameBody; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + /** + * latest ground location indicated by the mouse cursor + */ + final private static Vector3f mouseLocation = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloDoor application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloDoor application = new HelloDoor(); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + + // Enable gamma correction for accurate lighting. + settings.setGammaCorrection(true); + + // Disable VSync for more frequent mouse-position updates. + settings.setVSync(false); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a static plane to represent the floor. + addPlane(groundY); + + // Add a static body for the door frame. + addDoorFrame(); + + // Add a dynamic body for the door. + addDoor(); + + // Add a single-ended physics joint to constrain the door's motion. + Vector3f pivotInDoor = new Vector3f(-doorHalfWidth, 0f, 0f); + Vector3f pivotInWorld = new Vector3f(-doorHalfWidth, 0f, 0f); + HingeJoint joint = new HingeJoint(doorBody, pivotInDoor, pivotInWorld, + Vector3f.UNIT_Y, Vector3f.UNIT_Y, JointEnd.B); + float lowLimitAngle = -2f; + float highLimitAngle = 2f; + joint.setLimit(lowLimitAngle, highLimitAngle); + physicsSpace.addJoint(joint); + + // Disable collisions between the door and the door frame. + doorBody.addToIgnoreList(doorFrameBody); + + // Add a kinematic, yellow ball. + ballBody = addBall(); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Calculate the ground location (if any) selected by the mouse cursor. + Vector2f screenXy = inputManager.getCursorPosition(); + float nearZ = 0f; + Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); + float farZ = 1f; + Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); + if (nearLocation.y > groundY && farLocation.y < groundY) { + float dy = nearLocation.y - farLocation.y; + float t = (nearLocation.y - groundY) / dy; + FastMath.interpolateLinear( + t, nearLocation, farLocation, mouseLocation); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Reposition the ball based on the mouse location. + Vector3f bodyLocation = mouseLocation.add(0f, doorHalfHeight, 0f); + ballBody.setPhysicsLocation(bodyLocation); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Create a kinematic rigid body with a sphere shape and add it to the + * space. + * + * @return the new body + */ + private PhysicsRigidBody addBall() { + float radius = 0.4f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + result.setKinematic(true); + physicsSpace.addCollisionObject(result); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + + return result; + } + + /** + * Create a dynamic body with a box shape and add it to the space. + */ + private void addDoor() { + BoxCollisionShape shape = new BoxCollisionShape( + doorHalfWidth, doorHalfHeight, halfThickness); + float mass = 1f; + doorBody = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(doorBody); + + // Disable sleep (deactivation). + doorBody.setEnableSleep(false); + + Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); + doorBody.setDebugMaterial(redMaterial); + doorBody.setDebugMeshNormals(MeshNormals.Facet); + } + + /** + * Add a static door frame to the space. + */ + private void addDoorFrame() { + float frameHalfWidth = 0.5f; + BoxCollisionShape jambShape = new BoxCollisionShape( + frameHalfWidth, doorHalfHeight, halfThickness); + + float lintelLength = doorHalfWidth + 2 * frameHalfWidth; + BoxCollisionShape lintelShape = new BoxCollisionShape( + lintelLength, frameHalfWidth, halfThickness); + + CompoundCollisionShape shape = new CompoundCollisionShape(); + shape.addChildShape(jambShape, doorHalfWidth + frameHalfWidth, 0f, 0f); + shape.addChildShape(jambShape, -doorHalfWidth - frameHalfWidth, 0f, 0f); + shape.addChildShape( + lintelShape, 0f, doorHalfHeight + frameHalfWidth, 0f); + + doorFrameBody = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + Material grayMaterial = createLitMaterial(0.5f, 0.5f, 0.5f); + doorFrameBody.setDebugMaterial(grayMaterial); + doorFrameBody.setDebugMeshNormals(MeshNormals.Facet); + + physicsSpace.addCollisionObject(doorFrameBody); + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + } + + /** + * Add a horizontal plane body to the space. + * + * @param y (the desired elevation, in physics-space coordinates) + */ + private void addPlane(float y) { + Plane plane = new Plane(Vector3f.UNIT_Y, y); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody floorBody + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + // Load a repeating tile texture. + String assetPath = "Textures/greenTile.png"; + boolean flipY = false; + TextureKey key = new TextureKey(assetPath, flipY); + boolean generateMips = true; + key.setGenerateMips(generateMips); + Texture texture = assetManager.loadTexture(key); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + + // Enable anisotropic filtering, to reduce blurring. + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + texture.setAnisotropicFilter(degree); + + // Apply a tiled, unshaded debug material to the body. + Material material = new Material(assetManager, Materials.UNSHADED); + material.setTexture("ColorMap", texture); + floorBody.setDebugMaterial(material); + + // Generate texture coordinates during debug-mesh initialization. + float tileSize = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); + floorBody.setDebugMeshInitListener(planeDmiListener); + + physicsSpace.addCollisionObject(floorBody); + } + + /** + * Disable FlyByCamera during startup. + */ + private void configureCamera() { + flyCam.setEnabled(false); + + cam.setLocation(new Vector3f(0f, 12f, 10f)); + cam.setRotation(new Quaternion(0f, 0.9f, -0.43589f, 0f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoubleEnded.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoubleEnded.java index b5a061957..dde5aae42 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoubleEnded.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloDoubleEnded.java @@ -1,374 +1,374 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.texture.Texture; -import jme3utilities.MeshNormals; - -/** - * A simple example of a double-ended PhysicsJoint. - *

- * Builds upon HelloJoint. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloDoubleEnded - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * physics-space Y coordinate of the ground plane - */ - final private static float groundY = -4f; - /** - * half the height of the paddle (in physics-space units) - */ - final private static float paddleHalfHeight = 1f; - // ************************************************************************* - // fields - - /** - * mouse-controlled kinematic paddle - */ - private static PhysicsRigidBody paddleBody; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - /** - * latest ground location indicated by the mouse cursor - */ - final private static Vector3f mouseLocation = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloDoubleEnded application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloDoubleEnded application = new HelloDoubleEnded(); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - - // Enable gamma correction for accurate lighting. - settings.setGammaCorrection(true); - - // Disable VSync for more frequent mouse-position updates. - settings.setVSync(false); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a static plane to represent the ground. - addPlane(groundY); - - // Add a mouse-controlled kinematic paddle. - addPaddle(); - - // Add a dynamic ball. - PhysicsRigidBody ballBody = addBall(); - - // Add a double-ended physics joint to connect the ball to the paddle. - Vector3f pivotInBall = new Vector3f(0f, 3f, 0f); - Vector3f pivotInPaddle = new Vector3f(0f, 3f, 0f); - Matrix3f rotInBall = Matrix3f.IDENTITY; - Matrix3f rotInPaddle = Matrix3f.IDENTITY; - New6Dof joint = new New6Dof( - ballBody, paddleBody, pivotInBall, pivotInPaddle, - rotInBall, rotInPaddle, RotationOrder.XYZ); - physicsSpace.addJoint(joint); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Calculate the ground location (if any) indicated by the mouse cursor. - Vector2f screenXy = inputManager.getCursorPosition(); - float nearZ = 0f; - Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); - float farZ = 1f; - Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); - if (nearLocation.y > groundY && farLocation.y < groundY) { - float dy = nearLocation.y - farLocation.y; - float t = (nearLocation.y - groundY) / dy; - FastMath.interpolateLinear( - t, nearLocation, farLocation, mouseLocation); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Reposition the paddle based on the mouse location. - Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); - paddleBody.setPhysicsLocation(bodyLocation); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Create a dynamic rigid body with a sphere shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addBall() { - float radius = 0.4f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(result); - - // Disable sleep (deactivation). - result.setEnableSleep(false); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - result.setDebugMeshNormals(MeshNormals.Facet); - // faceted so that rotations will be visible - - return result; - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - } - - /** - * Create a kinematic body with a box shape and add it to the space. - */ - private void addPaddle() { - BoxCollisionShape shape - = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); - paddleBody = new PhysicsRigidBody(shape); - paddleBody.setKinematic(true); - - physicsSpace.addCollisionObject(paddleBody); - - Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); - paddleBody.setDebugMaterial(redMaterial); - paddleBody.setDebugMeshNormals(MeshNormals.Facet); - } - - /** - * Add a horizontal plane body to the space. - * - * @param y (the desired elevation, in physics-space coordinates) - */ - private void addPlane(float y) { - Plane plane = new Plane(Vector3f.UNIT_Y, y); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - // Load a repeating tile texture. - String assetPath = "Textures/greenTile.png"; - boolean flipY = false; - TextureKey key = new TextureKey(assetPath, flipY); - boolean generateMips = true; - key.setGenerateMips(generateMips); - Texture texture = assetManager.loadTexture(key); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - - // Enable anisotropic filtering, to reduce blurring. - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - texture.setAnisotropicFilter(degree); - - // Apply a tiled, unshaded debug material to the body. - Material material = new Material(assetManager, Materials.UNSHADED); - material.setTexture("ColorMap", texture); - body.setDebugMaterial(material); - - // Generate texture coordinates during debug-mesh initialization. - float tileSize = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); - body.setDebugMeshInitListener(planeDmiListener); - - physicsSpace.addCollisionObject(body); - } - - /** - * Disable FlyByCamera during startup. - */ - private void configureCamera() { - flyCam.setEnabled(false); - - cam.setLocation(new Vector3f(0f, 5f, 10f)); - cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import jme3utilities.MeshNormals; + +/** + * A simple example of a double-ended PhysicsJoint. + *

+ * Builds upon HelloJoint. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloDoubleEnded + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * physics-space Y coordinate of the ground plane + */ + final private static float groundY = -4f; + /** + * half the height of the paddle (in physics-space units) + */ + final private static float paddleHalfHeight = 1f; + // ************************************************************************* + // fields + + /** + * mouse-controlled kinematic paddle + */ + private static PhysicsRigidBody paddleBody; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + /** + * latest ground location indicated by the mouse cursor + */ + final private static Vector3f mouseLocation = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloDoubleEnded application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloDoubleEnded application = new HelloDoubleEnded(); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + + // Enable gamma correction for accurate lighting. + settings.setGammaCorrection(true); + + // Disable VSync for more frequent mouse-position updates. + settings.setVSync(false); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a static plane to represent the ground. + addPlane(groundY); + + // Add a mouse-controlled kinematic paddle. + addPaddle(); + + // Add a dynamic ball. + PhysicsRigidBody ballBody = addBall(); + + // Add a double-ended physics joint to connect the ball to the paddle. + Vector3f pivotInBall = new Vector3f(0f, 3f, 0f); + Vector3f pivotInPaddle = new Vector3f(0f, 3f, 0f); + Matrix3f rotInBall = Matrix3f.IDENTITY; + Matrix3f rotInPaddle = Matrix3f.IDENTITY; + New6Dof joint = new New6Dof( + ballBody, paddleBody, pivotInBall, pivotInPaddle, + rotInBall, rotInPaddle, RotationOrder.XYZ); + physicsSpace.addJoint(joint); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Calculate the ground location (if any) indicated by the mouse cursor. + Vector2f screenXy = inputManager.getCursorPosition(); + float nearZ = 0f; + Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); + float farZ = 1f; + Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); + if (nearLocation.y > groundY && farLocation.y < groundY) { + float dy = nearLocation.y - farLocation.y; + float t = (nearLocation.y - groundY) / dy; + FastMath.interpolateLinear( + t, nearLocation, farLocation, mouseLocation); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Reposition the paddle based on the mouse location. + Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); + paddleBody.setPhysicsLocation(bodyLocation); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Create a dynamic rigid body with a sphere shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addBall() { + float radius = 0.4f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(result); + + // Disable sleep (deactivation). + result.setEnableSleep(false); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + result.setDebugMeshNormals(MeshNormals.Facet); + // faceted so that rotations will be visible + + return result; + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + } + + /** + * Create a kinematic body with a box shape and add it to the space. + */ + private void addPaddle() { + BoxCollisionShape shape + = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); + paddleBody = new PhysicsRigidBody(shape); + paddleBody.setKinematic(true); + + physicsSpace.addCollisionObject(paddleBody); + + Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); + paddleBody.setDebugMaterial(redMaterial); + paddleBody.setDebugMeshNormals(MeshNormals.Facet); + } + + /** + * Add a horizontal plane body to the space. + * + * @param y (the desired elevation, in physics-space coordinates) + */ + private void addPlane(float y) { + Plane plane = new Plane(Vector3f.UNIT_Y, y); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + // Load a repeating tile texture. + String assetPath = "Textures/greenTile.png"; + boolean flipY = false; + TextureKey key = new TextureKey(assetPath, flipY); + boolean generateMips = true; + key.setGenerateMips(generateMips); + Texture texture = assetManager.loadTexture(key); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + + // Enable anisotropic filtering, to reduce blurring. + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + texture.setAnisotropicFilter(degree); + + // Apply a tiled, unshaded debug material to the body. + Material material = new Material(assetManager, Materials.UNSHADED); + material.setTexture("ColorMap", texture); + body.setDebugMaterial(material); + + // Generate texture coordinates during debug-mesh initialization. + float tileSize = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); + body.setDebugMeshInitListener(planeDmiListener); + + physicsSpace.addCollisionObject(body); + } + + /** + * Disable FlyByCamera during startup. + */ + private void configureCamera() { + flyCam.setEnabled(false); + + cam.setLocation(new Vector3f(0f, 5f, 10f)); + cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloJoint.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloJoint.java index 38eb68073..286495347 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloJoint.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloJoint.java @@ -1,373 +1,373 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.texture.Texture; -import jme3utilities.MeshNormals; - -/** - * A simple example of a PhysicsJoint. - *

- * Builds upon HelloKinematics. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloJoint - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * physics-space Y coordinate of the ground plane - */ - final private static float groundY = -4f; - /** - * half the height of the paddle (in physics-space units) - */ - final private static float paddleHalfHeight = 1f; - // ************************************************************************* - // fields - - /** - * mouse-controlled kinematic paddle - */ - private static PhysicsRigidBody paddleBody; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - /** - * latest ground location indicated by the mouse cursor - */ - final private static Vector3f mouseLocation = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloJoint application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloJoint application = new HelloJoint(); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - - // Enable gamma correction for accurate lighting. - settings.setGammaCorrection(true); - - // Disable VSync for more frequent mouse-position updates. - settings.setVSync(false); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a static plane to represent the ground. - addPlane(groundY); - - // Add a mouse-controlled kinematic paddle. - addPaddle(); - - // Add a dynamic yellow ball. - PhysicsRigidBody ballBody = addBall(); - - // Add a single-ended physics joint to constrain the ball's motion. - Vector3f pivotInBall = new Vector3f(0f, 3f, 0f); - Vector3f pivotInWorld = new Vector3f(0f, groundY + 4f, 0f); - Matrix3f rotInBall = Matrix3f.IDENTITY; - Matrix3f rotInWorld = Matrix3f.IDENTITY; - New6Dof joint = new New6Dof(ballBody, pivotInBall, pivotInWorld, - rotInBall, rotInWorld, RotationOrder.XYZ); - physicsSpace.addJoint(joint); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Calculate the ground location (if any) selected by the mouse cursor. - Vector2f screenXy = inputManager.getCursorPosition(); - float nearZ = 0f; - Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); - float farZ = 1f; - Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); - if (nearLocation.y > groundY && farLocation.y < groundY) { - float dy = nearLocation.y - farLocation.y; - float t = (nearLocation.y - groundY) / dy; - FastMath.interpolateLinear( - t, nearLocation, farLocation, mouseLocation); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Reposition the paddle based on the mouse location. - Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); - paddleBody.setPhysicsLocation(bodyLocation); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Create a dynamic rigid body with a sphere shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addBall() { - float radius = 0.4f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(result); - - // Disable sleep (deactivation). - result.setEnableSleep(false); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - result.setDebugMeshNormals(MeshNormals.Facet); - // faceted so that rotations will be visible - - return result; - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - } - - /** - * Create a kinematic body with a box shape and add it to the space. - */ - private void addPaddle() { - BoxCollisionShape shape - = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); - paddleBody = new PhysicsRigidBody(shape); - paddleBody.setKinematic(true); - - physicsSpace.addCollisionObject(paddleBody); - - Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); - paddleBody.setDebugMaterial(redMaterial); - paddleBody.setDebugMeshNormals(MeshNormals.Facet); - } - - /** - * Add a horizontal plane body to the space. - * - * @param y (the desired elevation, in physics-space coordinates) - */ - private void addPlane(float y) { - Plane plane = new Plane(Vector3f.UNIT_Y, y); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - // Load a repeating tile texture. - String assetPath = "Textures/greenTile.png"; - boolean flipY = false; - TextureKey key = new TextureKey(assetPath, flipY); - boolean generateMips = true; - key.setGenerateMips(generateMips); - Texture texture = assetManager.loadTexture(key); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - - // Enable anisotropic filtering, to reduce blurring. - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - texture.setAnisotropicFilter(degree); - - // Apply a tiled, unshaded debug material to the body. - Material material = new Material(assetManager, Materials.UNSHADED); - material.setTexture("ColorMap", texture); - body.setDebugMaterial(material); - - // Generate texture coordinates during debug-mesh initialization. - float tileSize = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); - body.setDebugMeshInitListener(planeDmiListener); - - physicsSpace.addCollisionObject(body); - } - - /** - * Disable FlyByCamera during startup. - */ - private void configureCamera() { - flyCam.setEnabled(false); - - cam.setLocation(new Vector3f(0f, 5f, 10f)); - cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import jme3utilities.MeshNormals; + +/** + * A simple example of a PhysicsJoint. + *

+ * Builds upon HelloKinematics. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloJoint + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * physics-space Y coordinate of the ground plane + */ + final private static float groundY = -4f; + /** + * half the height of the paddle (in physics-space units) + */ + final private static float paddleHalfHeight = 1f; + // ************************************************************************* + // fields + + /** + * mouse-controlled kinematic paddle + */ + private static PhysicsRigidBody paddleBody; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + /** + * latest ground location indicated by the mouse cursor + */ + final private static Vector3f mouseLocation = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloJoint application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloJoint application = new HelloJoint(); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + + // Enable gamma correction for accurate lighting. + settings.setGammaCorrection(true); + + // Disable VSync for more frequent mouse-position updates. + settings.setVSync(false); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a static plane to represent the ground. + addPlane(groundY); + + // Add a mouse-controlled kinematic paddle. + addPaddle(); + + // Add a dynamic yellow ball. + PhysicsRigidBody ballBody = addBall(); + + // Add a single-ended physics joint to constrain the ball's motion. + Vector3f pivotInBall = new Vector3f(0f, 3f, 0f); + Vector3f pivotInWorld = new Vector3f(0f, groundY + 4f, 0f); + Matrix3f rotInBall = Matrix3f.IDENTITY; + Matrix3f rotInWorld = Matrix3f.IDENTITY; + New6Dof joint = new New6Dof(ballBody, pivotInBall, pivotInWorld, + rotInBall, rotInWorld, RotationOrder.XYZ); + physicsSpace.addJoint(joint); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Calculate the ground location (if any) selected by the mouse cursor. + Vector2f screenXy = inputManager.getCursorPosition(); + float nearZ = 0f; + Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); + float farZ = 1f; + Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); + if (nearLocation.y > groundY && farLocation.y < groundY) { + float dy = nearLocation.y - farLocation.y; + float t = (nearLocation.y - groundY) / dy; + FastMath.interpolateLinear( + t, nearLocation, farLocation, mouseLocation); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Reposition the paddle based on the mouse location. + Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); + paddleBody.setPhysicsLocation(bodyLocation); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Create a dynamic rigid body with a sphere shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addBall() { + float radius = 0.4f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(result); + + // Disable sleep (deactivation). + result.setEnableSleep(false); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + result.setDebugMeshNormals(MeshNormals.Facet); + // faceted so that rotations will be visible + + return result; + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + } + + /** + * Create a kinematic body with a box shape and add it to the space. + */ + private void addPaddle() { + BoxCollisionShape shape + = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); + paddleBody = new PhysicsRigidBody(shape); + paddleBody.setKinematic(true); + + physicsSpace.addCollisionObject(paddleBody); + + Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); + paddleBody.setDebugMaterial(redMaterial); + paddleBody.setDebugMeshNormals(MeshNormals.Facet); + } + + /** + * Add a horizontal plane body to the space. + * + * @param y (the desired elevation, in physics-space coordinates) + */ + private void addPlane(float y) { + Plane plane = new Plane(Vector3f.UNIT_Y, y); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + // Load a repeating tile texture. + String assetPath = "Textures/greenTile.png"; + boolean flipY = false; + TextureKey key = new TextureKey(assetPath, flipY); + boolean generateMips = true; + key.setGenerateMips(generateMips); + Texture texture = assetManager.loadTexture(key); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + + // Enable anisotropic filtering, to reduce blurring. + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + texture.setAnisotropicFilter(degree); + + // Apply a tiled, unshaded debug material to the body. + Material material = new Material(assetManager, Materials.UNSHADED); + material.setTexture("ColorMap", texture); + body.setDebugMaterial(material); + + // Generate texture coordinates during debug-mesh initialization. + float tileSize = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); + body.setDebugMeshInitListener(planeDmiListener); + + physicsSpace.addCollisionObject(body); + } + + /** + * Disable FlyByCamera during startup. + */ + private void configureCamera() { + flyCam.setEnabled(false); + + cam.setLocation(new Vector3f(0f, 5f, 10f)); + cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematicRbc.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematicRbc.java index 5e8aa82a0..c5a4a118b 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematicRbc.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematicRbc.java @@ -1,190 +1,190 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Sphere; -import com.jme3.system.AppSettings; - -/** - * A simple example combining kinematic and dynamic rigid-body controls. - *

- * Builds upon HelloKinematics and HelloRbc. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloKinematicRbc extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * physics-simulation time (in seconds, ≥0) - */ - private static float elapsedTime = 0f; - /** - * kinematic ball, orbiting the origin - */ - private static Geometry kine; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloKinematicRbc application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloKinematicRbc application = new HelloKinematicRbc(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - PhysicsSpace physicsSpace = configurePhysics(); - - // Create a material and a mesh for balls. - float ballRadius = 1f; - Material ballMaterial = new Material(assetManager, Materials.LIGHTING); - Mesh ballMesh = new Sphere(16, 32, ballRadius); - - // Create geometries for a dynamic ball and a kinematic ball - // and add them to the scene graph. - Geometry dyna = new Geometry("dyna", ballMesh); - dyna.setMaterial(ballMaterial); - rootNode.attachChild(dyna); - - kine = new Geometry("kine", ballMesh); - kine.setMaterial(ballMaterial); - rootNode.attachChild(kine); - - // Create RBCs for both balls and add them to the geometries. - float mass = 2f; - RigidBodyControl dynaRbc = new RigidBodyControl(mass); - dyna.addControl(dynaRbc); - RigidBodyControl kineRbc = new RigidBodyControl(mass); - kine.addControl(kineRbc); - - // Add the controls to the physics space. - dynaRbc.setPhysicsSpace(physicsSpace); - kineRbc.setPhysicsSpace(physicsSpace); - - // Position the dynamic ball in physics space. - dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Set the kinematic flag on the other ball. - kineRbc.setKinematic(true); - - // Add lighting. - addLighting(rootNode); - - // Minie's BulletAppState simulates the dynamics... - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Cause the kinematic ball to orbit the origin. - float orbitalPeriod = 0.8f; // seconds - float phaseAngle = elapsedTime * FastMath.TWO_PI / orbitalPeriod; - - float orbitRadius = 0.4f; // world units - float x = orbitRadius * FastMath.sin(phaseAngle); - float y = orbitRadius * FastMath.cos(phaseAngle); - Vector3f location = new Vector3f(x, y, 0f); - kine.setLocalTranslation(location); - - elapsedTime += tpf; - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private static void addLighting(Spatial scene) { - // Light the scene with ambient and directional lights. - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * A simple example combining kinematic and dynamic rigid-body controls. + *

+ * Builds upon HelloKinematics and HelloRbc. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloKinematicRbc extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * physics-simulation time (in seconds, ≥0) + */ + private static float elapsedTime = 0f; + /** + * kinematic ball, orbiting the origin + */ + private static Geometry kine; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloKinematicRbc application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloKinematicRbc application = new HelloKinematicRbc(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + PhysicsSpace physicsSpace = configurePhysics(); + + // Create a material and a mesh for balls. + float ballRadius = 1f; + Material ballMaterial = new Material(assetManager, Materials.LIGHTING); + Mesh ballMesh = new Sphere(16, 32, ballRadius); + + // Create geometries for a dynamic ball and a kinematic ball + // and add them to the scene graph. + Geometry dyna = new Geometry("dyna", ballMesh); + dyna.setMaterial(ballMaterial); + rootNode.attachChild(dyna); + + kine = new Geometry("kine", ballMesh); + kine.setMaterial(ballMaterial); + rootNode.attachChild(kine); + + // Create RBCs for both balls and add them to the geometries. + float mass = 2f; + RigidBodyControl dynaRbc = new RigidBodyControl(mass); + dyna.addControl(dynaRbc); + RigidBodyControl kineRbc = new RigidBodyControl(mass); + kine.addControl(kineRbc); + + // Add the controls to the physics space. + dynaRbc.setPhysicsSpace(physicsSpace); + kineRbc.setPhysicsSpace(physicsSpace); + + // Position the dynamic ball in physics space. + dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Set the kinematic flag on the other ball. + kineRbc.setKinematic(true); + + // Add lighting. + addLighting(rootNode); + + // Minie's BulletAppState simulates the dynamics... + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Cause the kinematic ball to orbit the origin. + float orbitalPeriod = 0.8f; // seconds + float phaseAngle = elapsedTime * FastMath.TWO_PI / orbitalPeriod; + + float orbitRadius = 0.4f; // world units + float x = orbitRadius * FastMath.sin(phaseAngle); + float y = orbitRadius * FastMath.cos(phaseAngle); + Vector3f location = new Vector3f(x, y, 0f); + kine.setLocalTranslation(location); + + elapsedTime += tpf; + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private static void addLighting(Spatial scene) { + // Light the scene with ambient and directional lights. + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematics.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematics.java index 06bd2c53b..ebf4a1db9 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematics.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloKinematics.java @@ -1,142 +1,142 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; - -/** - * A simple example combining kinematic and dynamic rigid bodies. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloKinematics - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - /** - * physics-simulation time (in seconds, ≥0) - */ - private static float elapsedTime = 0f; - /** - * kinematic ball, orbiting the origin - */ - private static PhysicsRigidBody kineBall; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloKinematics application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloKinematics application = new HelloKinematics(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - physicsSpace.addTickListener(this); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Create a CollisionShape for balls. - float ballRadius = 1f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - - // Create a dynamic body and add it to the space. - float mass = 2f; - PhysicsRigidBody dynaBall = new PhysicsRigidBody(ballShape, mass); - physicsSpace.addCollisionObject(dynaBall); - dynaBall.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Create a kinematic body and add it to the space. - kineBall = new PhysicsRigidBody(ballShape); - physicsSpace.addCollisionObject(kineBall); - kineBall.setKinematic(true); - - // Minie's BulletAppState simulates the dynamics... - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Make the kinematic ball orbit the origin. - float orbitalPeriod = 0.8f; // seconds - float phaseAngle = elapsedTime * FastMath.TWO_PI / orbitalPeriod; - - float orbitRadius = 0.4f; // physics-space units - float x = orbitRadius * FastMath.sin(phaseAngle); - float y = orbitRadius * FastMath.cos(phaseAngle); - Vector3f location = new Vector3f(x, y, 0f); - kineBall.setPhysicsLocation(location); - - elapsedTime += timeStep; - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; + +/** + * A simple example combining kinematic and dynamic rigid bodies. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloKinematics + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + /** + * physics-simulation time (in seconds, ≥0) + */ + private static float elapsedTime = 0f; + /** + * kinematic ball, orbiting the origin + */ + private static PhysicsRigidBody kineBall; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloKinematics application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloKinematics application = new HelloKinematics(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + physicsSpace.addTickListener(this); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Create a CollisionShape for balls. + float ballRadius = 1f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + + // Create a dynamic body and add it to the space. + float mass = 2f; + PhysicsRigidBody dynaBall = new PhysicsRigidBody(ballShape, mass); + physicsSpace.addCollisionObject(dynaBall); + dynaBall.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Create a kinematic body and add it to the space. + kineBall = new PhysicsRigidBody(ballShape); + physicsSpace.addCollisionObject(kineBall); + kineBall.setKinematic(true); + + // Minie's BulletAppState simulates the dynamics... + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Make the kinematic ball orbit the origin. + float orbitalPeriod = 0.8f; // seconds + float phaseAngle = elapsedTime * FastMath.TWO_PI / orbitalPeriod; + + float orbitRadius = 0.4f; // physics-space units + float x = orbitRadius * FastMath.sin(phaseAngle); + float y = orbitRadius * FastMath.cos(phaseAngle); + Vector3f location = new Vector3f(x, y, 0f); + kineBall.setPhysicsLocation(location); + + elapsedTime += timeStep; + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLimit.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLimit.java index 93c16e902..ff95cac91 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLimit.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLimit.java @@ -1,380 +1,380 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.Box2dShape; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import jme3utilities.MeshNormals; - -/** - * A simple example of a PhysicsJoint with limits. - *

- * Builds upon HelloJoint. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloLimit - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * physics-space Y coordinate of the ground plane - */ - final private static float groundY = -2f; - /** - * half the height of the paddle (in physics-space units) - */ - final private static float paddleHalfHeight = 1f; - // ************************************************************************* - // fields - - /** - * mouse-controlled kinematic paddle - */ - private static PhysicsRigidBody paddleBody; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - /** - * latest ground location indicated by the mouse cursor - */ - final private static Vector3f mouseLocation = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloLimit application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloLimit application = new HelloLimit(); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - - // Enable gamma correction for accurate lighting. - settings.setGammaCorrection(true); - - // Disable VSync for more frequent mouse-position updates. - settings.setVSync(false); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a static, green square to represent the ground. - float halfExtent = 3f; - addSquare(halfExtent, groundY); - - // Add a mouse-controlled kinematic paddle. - addPaddle(); - - // Add a dynamic yellow ball. - PhysicsRigidBody ballBody = addBall(); - - // Add a single-ended physics joint to constrain the ball's center. - Vector3f pivotInBall = new Vector3f(0f, 0f, 0f); - Vector3f pivotInWorld = new Vector3f(0f, 0f, 0f); - Matrix3f rotInBall = Matrix3f.IDENTITY; - Matrix3f rotInPaddle = Matrix3f.IDENTITY; - New6Dof joint = new New6Dof(ballBody, pivotInBall, pivotInWorld, - rotInBall, rotInPaddle, RotationOrder.XYZ); - physicsSpace.addJoint(joint); - - // Limit the X and Z translation DOFs. - joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_X, -halfExtent); - joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Z, -halfExtent); - joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_X, +halfExtent); - joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Z, +halfExtent); - - // Lock the Y translation at paddle height. - float paddleY = groundY + paddleHalfHeight; - joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Y, paddleY); - joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Y, paddleY); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Calculate the ground location (if any) selected by the mouse cursor. - Vector2f screenXy = inputManager.getCursorPosition(); - float nearZ = 0f; - Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); - float farZ = 1f; - Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); - if (nearLocation.y > groundY && farLocation.y < groundY) { - float dy = nearLocation.y - farLocation.y; - float t = (nearLocation.y - groundY) / dy; - FastMath.interpolateLinear( - t, nearLocation, farLocation, mouseLocation); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Reposition the paddle based on the mouse location. - Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); - paddleBody.setPhysicsLocation(bodyLocation); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Create a dynamic rigid body with a sphere shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addBall() { - float radius = 0.4f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(result); - - // Apply angular damping to reduce the ball's tendency to spin. - result.setAngularDamping(0.6f); - - // Disable sleep (deactivation). - result.setEnableSleep(false); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - result.setDebugMeshNormals(MeshNormals.Facet); - // faceted so that rotations will be visible - - return result; - } - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Create a kinematic body with a box shape and add it to the space. - */ - private void addPaddle() { - BoxCollisionShape shape - = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); - paddleBody = new PhysicsRigidBody(shape); - paddleBody.setKinematic(true); - - physicsSpace.addCollisionObject(paddleBody); - - Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); - paddleBody.setDebugMaterial(redMaterial); - paddleBody.setDebugMeshNormals(MeshNormals.Facet); - } - - /** - * Add a horizontal square body to the space. - * - * @param halfExtent (half of the desired side length) - * @param y (the desired elevation, in physics-space coordinates) - * @return the new body (not null) - */ - private PhysicsRigidBody addSquare(float halfExtent, float y) { - // Construct a static rigid body with a square shape. - Box2dShape shape = new Box2dShape(halfExtent); - PhysicsRigidBody result - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - Material greenMaterial = createLitMaterial(0f, 1f, 0f); - result.setDebugMaterial(greenMaterial); - result.setDebugMeshNormals(MeshNormals.Facet); - - physicsSpace.addCollisionObject(result); - - // Rotate it 90 degrees to a horizontal orientation. - Quaternion rotate90 - = new Quaternion().fromAngles(-FastMath.HALF_PI, 0f, 0f); - result.setPhysicsRotation(rotate90); - - // Translate it to the desired elevation. - result.setPhysicsLocation(new Vector3f(0f, y, 0f)); - - return result; - } - - /** - * Disable FlyByCamera during startup. - */ - private void configureCamera() { - flyCam.setEnabled(false); - - cam.setLocation(new Vector3f(0f, 5f, 10f)); - cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.Box2dShape; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import jme3utilities.MeshNormals; + +/** + * A simple example of a PhysicsJoint with limits. + *

+ * Builds upon HelloJoint. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloLimit + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * physics-space Y coordinate of the ground plane + */ + final private static float groundY = -2f; + /** + * half the height of the paddle (in physics-space units) + */ + final private static float paddleHalfHeight = 1f; + // ************************************************************************* + // fields + + /** + * mouse-controlled kinematic paddle + */ + private static PhysicsRigidBody paddleBody; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + /** + * latest ground location indicated by the mouse cursor + */ + final private static Vector3f mouseLocation = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloLimit application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloLimit application = new HelloLimit(); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + + // Enable gamma correction for accurate lighting. + settings.setGammaCorrection(true); + + // Disable VSync for more frequent mouse-position updates. + settings.setVSync(false); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a static, green square to represent the ground. + float halfExtent = 3f; + addSquare(halfExtent, groundY); + + // Add a mouse-controlled kinematic paddle. + addPaddle(); + + // Add a dynamic yellow ball. + PhysicsRigidBody ballBody = addBall(); + + // Add a single-ended physics joint to constrain the ball's center. + Vector3f pivotInBall = new Vector3f(0f, 0f, 0f); + Vector3f pivotInWorld = new Vector3f(0f, 0f, 0f); + Matrix3f rotInBall = Matrix3f.IDENTITY; + Matrix3f rotInPaddle = Matrix3f.IDENTITY; + New6Dof joint = new New6Dof(ballBody, pivotInBall, pivotInWorld, + rotInBall, rotInPaddle, RotationOrder.XYZ); + physicsSpace.addJoint(joint); + + // Limit the X and Z translation DOFs. + joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_X, -halfExtent); + joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Z, -halfExtent); + joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_X, +halfExtent); + joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Z, +halfExtent); + + // Lock the Y translation at paddle height. + float paddleY = groundY + paddleHalfHeight; + joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Y, paddleY); + joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Y, paddleY); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Calculate the ground location (if any) selected by the mouse cursor. + Vector2f screenXy = inputManager.getCursorPosition(); + float nearZ = 0f; + Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); + float farZ = 1f; + Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); + if (nearLocation.y > groundY && farLocation.y < groundY) { + float dy = nearLocation.y - farLocation.y; + float t = (nearLocation.y - groundY) / dy; + FastMath.interpolateLinear( + t, nearLocation, farLocation, mouseLocation); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Reposition the paddle based on the mouse location. + Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); + paddleBody.setPhysicsLocation(bodyLocation); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Create a dynamic rigid body with a sphere shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addBall() { + float radius = 0.4f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(result); + + // Apply angular damping to reduce the ball's tendency to spin. + result.setAngularDamping(0.6f); + + // Disable sleep (deactivation). + result.setEnableSleep(false); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + result.setDebugMeshNormals(MeshNormals.Facet); + // faceted so that rotations will be visible + + return result; + } + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Create a kinematic body with a box shape and add it to the space. + */ + private void addPaddle() { + BoxCollisionShape shape + = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); + paddleBody = new PhysicsRigidBody(shape); + paddleBody.setKinematic(true); + + physicsSpace.addCollisionObject(paddleBody); + + Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); + paddleBody.setDebugMaterial(redMaterial); + paddleBody.setDebugMeshNormals(MeshNormals.Facet); + } + + /** + * Add a horizontal square body to the space. + * + * @param halfExtent (half of the desired side length) + * @param y (the desired elevation, in physics-space coordinates) + * @return the new body (not null) + */ + private PhysicsRigidBody addSquare(float halfExtent, float y) { + // Construct a static rigid body with a square shape. + Box2dShape shape = new Box2dShape(halfExtent); + PhysicsRigidBody result + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + Material greenMaterial = createLitMaterial(0f, 1f, 0f); + result.setDebugMaterial(greenMaterial); + result.setDebugMeshNormals(MeshNormals.Facet); + + physicsSpace.addCollisionObject(result); + + // Rotate it 90 degrees to a horizontal orientation. + Quaternion rotate90 + = new Quaternion().fromAngles(-FastMath.HALF_PI, 0f, 0f); + result.setPhysicsRotation(rotate90); + + // Translate it to the desired elevation. + result.setPhysicsLocation(new Vector3f(0f, y, 0f)); + + return result; + } + + /** + * Disable FlyByCamera during startup. + */ + private void configureCamera() { + flyCam.setEnabled(false); + + cam.setLocation(new Vector3f(0f, 5f, 10f)); + cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLocalPhysics.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLocalPhysics.java index 04f7e0961..9ed6eb7bd 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLocalPhysics.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloLocalPhysics.java @@ -1,193 +1,193 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Sphere; -import com.jme3.system.AppSettings; - -/** - * A simple example involving a RigidBodyControl with - * setApplyPhysicsLocal(true). - *

- * Builds upon HelloKinematicRbc. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloLocalPhysics extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * physics-simulation time (in seconds, ≥0) - */ - private static float elapsedTime = 0f; - /** - * node orbiting the origin - */ - private static Node orbitingNode; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloLocalPhysics application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloLocalPhysics application = new HelloLocalPhysics(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - PhysicsSpace physicsSpace = configurePhysics(); - - // Create a material and a mesh for balls. - float ballRadius = 1f; - Material ballMaterial = new Material(assetManager, Materials.LIGHTING); - Mesh ballMesh = new Sphere(16, 32, ballRadius); - - // Create an orbiting Node and add it to the scene graph. - orbitingNode = new Node("orbiting node"); - rootNode.attachChild(orbitingNode); - - // Create geometries for a dynamic ball and a kinematic ball - // and attach them to the orbiting node. - Geometry dyna = new Geometry("dyna", ballMesh); - dyna.setMaterial(ballMaterial); - orbitingNode.attachChild(dyna); - - Geometry kine = new Geometry("kine", ballMesh); - kine.setMaterial(ballMaterial); - orbitingNode.attachChild(kine); - - // Create local RBCs for both balls and add them to the geometries. - float mass = 2f; - RigidBodyControl dynaRbc = new RigidBodyControl(mass); - dynaRbc.setApplyPhysicsLocal(true); - dyna.addControl(dynaRbc); - RigidBodyControl kineRbc = new RigidBodyControl(mass); - kineRbc.setApplyPhysicsLocal(true); - kine.addControl(kineRbc); - - // Add the controls to the space. - dynaRbc.setPhysicsSpace(physicsSpace); - kineRbc.setPhysicsSpace(physicsSpace); - - // Position the dynamic ball in physics space. - dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Set the kinematic flag on the other ball. - kineRbc.setKinematic(true); - - addLighting(rootNode); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Cause the node to orbit the origin. - float orbitalPeriod = 0.8f; // seconds - float phaseAngle = elapsedTime * FastMath.TWO_PI / orbitalPeriod; - - float orbitRadius = 0.4f; // world units - float x = orbitRadius * FastMath.sin(phaseAngle); - float y = orbitRadius * FastMath.cos(phaseAngle); - Vector3f location = new Vector3f(x, y, 0f); - orbitingNode.setLocalTranslation(location); - - elapsedTime += tpf; - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private static void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * A simple example involving a RigidBodyControl with + * setApplyPhysicsLocal(true). + *

+ * Builds upon HelloKinematicRbc. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloLocalPhysics extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * physics-simulation time (in seconds, ≥0) + */ + private static float elapsedTime = 0f; + /** + * node orbiting the origin + */ + private static Node orbitingNode; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloLocalPhysics application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloLocalPhysics application = new HelloLocalPhysics(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + PhysicsSpace physicsSpace = configurePhysics(); + + // Create a material and a mesh for balls. + float ballRadius = 1f; + Material ballMaterial = new Material(assetManager, Materials.LIGHTING); + Mesh ballMesh = new Sphere(16, 32, ballRadius); + + // Create an orbiting Node and add it to the scene graph. + orbitingNode = new Node("orbiting node"); + rootNode.attachChild(orbitingNode); + + // Create geometries for a dynamic ball and a kinematic ball + // and attach them to the orbiting node. + Geometry dyna = new Geometry("dyna", ballMesh); + dyna.setMaterial(ballMaterial); + orbitingNode.attachChild(dyna); + + Geometry kine = new Geometry("kine", ballMesh); + kine.setMaterial(ballMaterial); + orbitingNode.attachChild(kine); + + // Create local RBCs for both balls and add them to the geometries. + float mass = 2f; + RigidBodyControl dynaRbc = new RigidBodyControl(mass); + dynaRbc.setApplyPhysicsLocal(true); + dyna.addControl(dynaRbc); + RigidBodyControl kineRbc = new RigidBodyControl(mass); + kineRbc.setApplyPhysicsLocal(true); + kine.addControl(kineRbc); + + // Add the controls to the space. + dynaRbc.setPhysicsSpace(physicsSpace); + kineRbc.setPhysicsSpace(physicsSpace); + + // Position the dynamic ball in physics space. + dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Set the kinematic flag on the other ball. + kineRbc.setKinematic(true); + + addLighting(rootNode); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Cause the node to orbit the origin. + float orbitalPeriod = 0.8f; // seconds + float phaseAngle = elapsedTime * FastMath.TWO_PI / orbitalPeriod; + + float orbitRadius = 0.4f; // world units + float x = orbitRadius * FastMath.sin(phaseAngle); + float y = orbitRadius * FastMath.cos(phaseAngle); + Vector3f location = new Vector3f(x, y, 0f); + orbitingNode.setLocalTranslation(location); + + elapsedTime += tpf; + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private static void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMadMallet.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMadMallet.java index ef0fbf14d..4182e7d0b 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMadMallet.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMadMallet.java @@ -1,123 +1,123 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example of a dynamic rigid body with an implausible center. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloMadMallet extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloMadMallet application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloMadMallet application = new HelloMadMallet(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - physicsSpace.setGravity(new Vector3f(0f, -50f, 0f)); - - // Visualize the local axes of each collision object. - bulletAppState.setDebugAxisLength(1f); - - // Construct a compound shape for the mallet. - float headLength = 1f; - float headRadius = 0.5f; - Vector3f hes = new Vector3f(headLength / 2f, headRadius, headRadius); - CollisionShape headShape - = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_X); - - float handleLength = 3f; - float handleRadius = 0.3f; - hes.set(handleRadius, handleRadius, handleLength / 2f); - CollisionShape handleShape - = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_Z); - - CompoundCollisionShape malletShape = new CompoundCollisionShape(); - malletShape.addChildShape(handleShape, 0f, 0f, handleLength / 2f); - malletShape.addChildShape(headShape, 0f, 0f, handleLength); - - // Create a dynamic body for the mallet. - float mass = 2f; - PhysicsRigidBody mallet = new PhysicsRigidBody(malletShape, mass); - mallet.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Increase the mallet's angular damping to stabilize it. - mallet.setAngularDamping(0.9f); - - physicsSpace.addCollisionObject(mallet); - - // Create a static disc and add it to the space. - float discRadius = 5f; - float discThickness = 0.5f; - CollisionShape discShape = new CylinderCollisionShape( - discRadius, discThickness, PhysicsSpace.AXIS_Y); - PhysicsRigidBody disc - = new PhysicsRigidBody(discShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(disc); - disc.setPhysicsLocation(new Vector3f(0f, -3f, 0f)); - - // Re-position the camera for a better view. - cam.setLocation(new Vector3f(10f, -2.75f, 0f)); - Vector3f targetLocation = new Vector3f(0f, -2.75f, 0f); - Vector3f upDirection = Vector3f.UNIT_Y; - cam.lookAt(targetLocation, upDirection); - - // Minie's BulletAppState simulates the dynamics... - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example of a dynamic rigid body with an implausible center. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloMadMallet extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloMadMallet application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloMadMallet application = new HelloMadMallet(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + physicsSpace.setGravity(new Vector3f(0f, -50f, 0f)); + + // Visualize the local axes of each collision object. + bulletAppState.setDebugAxisLength(1f); + + // Construct a compound shape for the mallet. + float headLength = 1f; + float headRadius = 0.5f; + Vector3f hes = new Vector3f(headLength / 2f, headRadius, headRadius); + CollisionShape headShape + = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_X); + + float handleLength = 3f; + float handleRadius = 0.3f; + hes.set(handleRadius, handleRadius, handleLength / 2f); + CollisionShape handleShape + = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_Z); + + CompoundCollisionShape malletShape = new CompoundCollisionShape(); + malletShape.addChildShape(handleShape, 0f, 0f, handleLength / 2f); + malletShape.addChildShape(headShape, 0f, 0f, handleLength); + + // Create a dynamic body for the mallet. + float mass = 2f; + PhysicsRigidBody mallet = new PhysicsRigidBody(malletShape, mass); + mallet.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Increase the mallet's angular damping to stabilize it. + mallet.setAngularDamping(0.9f); + + physicsSpace.addCollisionObject(mallet); + + // Create a static disc and add it to the space. + float discRadius = 5f; + float discThickness = 0.5f; + CollisionShape discShape = new CylinderCollisionShape( + discRadius, discThickness, PhysicsSpace.AXIS_Y); + PhysicsRigidBody disc + = new PhysicsRigidBody(discShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(disc); + disc.setPhysicsLocation(new Vector3f(0f, -3f, 0f)); + + // Re-position the camera for a better view. + cam.setLocation(new Vector3f(10f, -2.75f, 0f)); + Vector3f targetLocation = new Vector3f(0f, -2.75f, 0f); + Vector3f upDirection = Vector3f.UNIT_Y; + cam.lookAt(targetLocation, upDirection); + + // Minie's BulletAppState simulates the dynamics... + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMassDistribution.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMassDistribution.java index eae837cb1..540add382 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMassDistribution.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMassDistribution.java @@ -1,143 +1,143 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; - -/** - * A simple example to demonstrate the use of principalAxes() and correctAxes() - * to improve the plausibility of a compound shape. - *

- * Builds upon HelloMadMallet. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloMassDistribution extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloMassDistribution application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloMassDistribution application = new HelloMassDistribution(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - physicsSpace.setGravity(new Vector3f(0f, -50f, 0f)); - - // Visualize the local axes of each collision object. - bulletAppState.setDebugAxisLength(1f); - - // Construct a compound shape for the mallet. - float headLength = 1f; - float headRadius = 0.5f; - Vector3f hes = new Vector3f(headLength / 2f, headRadius, headRadius); - CollisionShape headShape - = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_X); - - float handleLength = 3f; - float handleRadius = 0.3f; - hes.set(handleRadius, handleRadius, handleLength / 2f); - CollisionShape handleShape - = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_Z); - - CompoundCollisionShape malletShape = new CompoundCollisionShape(); - malletShape.addChildShape(handleShape, 0f, 0f, handleLength / 2f); - malletShape.addChildShape(headShape, 0f, 0f, handleLength); - - // Calculate a correction to put 75% of the mass in the head. - float handleMass = 0.5f; - float headMass = 1.5f; - FloatBuffer massDistribution = BufferUtils.createFloatBuffer( - handleMass, headMass); - Vector3f inertiaVector = new Vector3f(); - Transform correction = malletShape.principalAxes( - massDistribution, null, inertiaVector); - - // Correct the shape. - malletShape.correctAxes(correction); - - // Create a dynamic body for the mallet. - float mass = handleMass + headMass; - PhysicsRigidBody mallet = new PhysicsRigidBody(malletShape, mass); - mallet.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - - // Increase the mallet's angular damping to stabilize it. - mallet.setAngularDamping(0.9f); - - // The mallet's center has changed, so adjust its moment of inertia. - Vector3f inverseInertia = Vector3f.UNIT_XYZ.divide(inertiaVector); - mallet.setInverseInertiaLocal(inverseInertia); - - physicsSpace.addCollisionObject(mallet); - - // Create a static disc and add it to the space. - float discRadius = 5f; - float discThickness = 0.5f; - CollisionShape discShape = new CylinderCollisionShape( - discRadius, discThickness, PhysicsSpace.AXIS_Y); - PhysicsRigidBody disc - = new PhysicsRigidBody(discShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(disc); - disc.setPhysicsLocation(new Vector3f(0f, -3f, 0f)); - - // Re-position the camera for a better view. - cam.setLocation(new Vector3f(10f, -2.75f, 0f)); - Vector3f targetLocation = new Vector3f(0f, -2.75f, 0f); - Vector3f upDirection = Vector3f.UNIT_Y; - cam.lookAt(targetLocation, upDirection); - - // Minie's BulletAppState simulates the dynamics... - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; + +/** + * A simple example to demonstrate the use of principalAxes() and correctAxes() + * to improve the plausibility of a compound shape. + *

+ * Builds upon HelloMadMallet. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloMassDistribution extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloMassDistribution application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloMassDistribution application = new HelloMassDistribution(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + physicsSpace.setGravity(new Vector3f(0f, -50f, 0f)); + + // Visualize the local axes of each collision object. + bulletAppState.setDebugAxisLength(1f); + + // Construct a compound shape for the mallet. + float headLength = 1f; + float headRadius = 0.5f; + Vector3f hes = new Vector3f(headLength / 2f, headRadius, headRadius); + CollisionShape headShape + = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_X); + + float handleLength = 3f; + float handleRadius = 0.3f; + hes.set(handleRadius, handleRadius, handleLength / 2f); + CollisionShape handleShape + = new CylinderCollisionShape(hes, PhysicsSpace.AXIS_Z); + + CompoundCollisionShape malletShape = new CompoundCollisionShape(); + malletShape.addChildShape(handleShape, 0f, 0f, handleLength / 2f); + malletShape.addChildShape(headShape, 0f, 0f, handleLength); + + // Calculate a correction to put 75% of the mass in the head. + float handleMass = 0.5f; + float headMass = 1.5f; + FloatBuffer massDistribution = BufferUtils.createFloatBuffer( + handleMass, headMass); + Vector3f inertiaVector = new Vector3f(); + Transform correction = malletShape.principalAxes( + massDistribution, null, inertiaVector); + + // Correct the shape. + malletShape.correctAxes(correction); + + // Create a dynamic body for the mallet. + float mass = handleMass + headMass; + PhysicsRigidBody mallet = new PhysicsRigidBody(malletShape, mass); + mallet.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + + // Increase the mallet's angular damping to stabilize it. + mallet.setAngularDamping(0.9f); + + // The mallet's center has changed, so adjust its moment of inertia. + Vector3f inverseInertia = Vector3f.UNIT_XYZ.divide(inertiaVector); + mallet.setInverseInertiaLocal(inverseInertia); + + physicsSpace.addCollisionObject(mallet); + + // Create a static disc and add it to the space. + float discRadius = 5f; + float discThickness = 0.5f; + CollisionShape discShape = new CylinderCollisionShape( + discRadius, discThickness, PhysicsSpace.AXIS_Y); + PhysicsRigidBody disc + = new PhysicsRigidBody(discShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(disc); + disc.setPhysicsLocation(new Vector3f(0f, -3f, 0f)); + + // Re-position the camera for a better view. + cam.setLocation(new Vector3f(10f, -2.75f, 0f)); + Vector3f targetLocation = new Vector3f(0f, -2.75f, 0f); + Vector3f upDirection = Vector3f.UNIT_Y; + cam.lookAt(targetLocation, upDirection); + + // Minie's BulletAppState simulates the dynamics... + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMinkowski.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMinkowski.java index 52e6f75ee..08cec0009 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMinkowski.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMinkowski.java @@ -1,89 +1,89 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.ConeCollisionShape; -import com.jme3.bullet.collision.shapes.MinkowskiSum; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example of a MinkowskiSum collision shape. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloMinkowski extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloMinkowski application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloMinkowski application = new HelloMinkowski(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize this application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add a static rigid body with a cone shape. - float radius = 1f; - float height = 1f; - ConeCollisionShape coneShape = new ConeCollisionShape(radius, height); - PhysicsRigidBody cone - = new PhysicsRigidBody(coneShape, PhysicsBody.massForStatic); - cone.setPhysicsLocation(new Vector3f(3f, 0f, 0f)); - physicsSpace.addCollisionObject(cone); - - // Add a static rigid body with a Minkowski-sum shape. - MinkowskiSum sumShape = new MinkowskiSum(coneShape, coneShape); - PhysicsRigidBody sum - = new PhysicsRigidBody(sumShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(sum); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.ConeCollisionShape; +import com.jme3.bullet.collision.shapes.MinkowskiSum; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example of a MinkowskiSum collision shape. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloMinkowski extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloMinkowski application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloMinkowski application = new HelloMinkowski(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize this application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add a static rigid body with a cone shape. + float radius = 1f; + float height = 1f; + ConeCollisionShape coneShape = new ConeCollisionShape(radius, height); + PhysicsRigidBody cone + = new PhysicsRigidBody(coneShape, PhysicsBody.massForStatic); + cone.setPhysicsLocation(new Vector3f(3f, 0f, 0f)); + physicsSpace.addCollisionObject(cone); + + // Add a static rigid body with a Minkowski-sum shape. + MinkowskiSum sumShape = new MinkowskiSum(coneShape, coneShape); + PhysicsRigidBody sum + = new PhysicsRigidBody(sumShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(sum); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMotor.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMotor.java index 4a1553523..d63efd142 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMotor.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloMotor.java @@ -1,292 +1,292 @@ -/* - Copyright (c) 2021-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.InputListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; - -/** - * A simple example of a PhysicsJoint with a motor. - *

- * Builds upon HelloLimit. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloMotor extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloMotor application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloMotor application = new HelloMotor(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a dynamic, green frame. - PhysicsRigidBody frameBody = addFrame(); - - // Add a dynamic, yellow box for the door. - PhysicsRigidBody doorBody = addDoor(); - - // Add a double-ended physics joint to join the door to the frame. - Vector3f pivotLocation = new Vector3f(-1f, 0f, 0f); - Quaternion pivotOrientation = Quaternion.IDENTITY; - New6Dof joint = New6Dof.newInstance(frameBody, doorBody, - pivotLocation, pivotOrientation, RotationOrder.XYZ); - physicsSpace.addJoint(joint); - - int xRotationDof = 3 + PhysicsSpace.AXIS_X; - int yRotationDof = 3 + PhysicsSpace.AXIS_Y; - int zRotationDof = 3 + PhysicsSpace.AXIS_Z; - - // Lock the X and Z rotation DOFs. - joint.set(MotorParam.LowerLimit, xRotationDof, 0f); - joint.set(MotorParam.LowerLimit, zRotationDof, 0f); - joint.set(MotorParam.UpperLimit, xRotationDof, 0f); - joint.set(MotorParam.UpperLimit, zRotationDof, 0f); - - // Limit the Y rotation DOF. - joint.set(MotorParam.LowerLimit, yRotationDof, 0f); - joint.set(MotorParam.UpperLimit, yRotationDof, 1.2f); - - // Enable the motor for Y rotation. - final RotationMotor motor = joint.getRotationMotor(PhysicsSpace.AXIS_Y); - motor.set(MotorParam.TargetVelocity, 0.4f); - motor.setMotorEnabled(true); - - // Configure the InputManager to respond to the spacebar. - inputManager.addMapping("reverse", new KeyTrigger(KeyInput.KEY_SPACE)); - InputListener actionListener = new ActionListener() { - @Override - public void onAction(String action, boolean ongoing, float tpf) { - if (action.equals("reverse") && ongoing) { - // Reverse the motor's direction. - float rate = motor.get(MotorParam.TargetVelocity); - motor.set(MotorParam.TargetVelocity, -rate); - } - } - }; - inputManager.addListener(actionListener, "reverse"); - } - // ************************************************************************* - // private methods - - /** - * Create a dynamic rigid body with a box shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addDoor() { - BoxCollisionShape shape = new BoxCollisionShape(0.8f, 0.8f, 0.1f); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(result); - - // Disable sleep (deactivation). - result.setEnableSleep(false); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - - return result; - } - - /** - * Create a dynamic body with a square-frame shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addFrame() { - CapsuleCollisionShape xShape - = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_X); - CapsuleCollisionShape yShape - = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_Y); - - CompoundCollisionShape frameShape = new CompoundCollisionShape(); - frameShape.addChildShape(xShape, 0f, +1f, 0f); - frameShape.addChildShape(xShape, 0f, -1f, 0f); - frameShape.addChildShape(yShape, +1f, 0f, 0f); - frameShape.addChildShape(yShape, -1f, 0f, 0f); - - PhysicsRigidBody result = new PhysicsRigidBody(frameShape); - physicsSpace.addCollisionObject(result); - - Material greenMaterial = createLitMaterial(0f, 1f, 0f); - result.setDebugMaterial(greenMaterial); - - return result; - } - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Position the camera during startup. - */ - private void configureCamera() { - flyCam.setMoveSpeed(5f); - - cam.setLocation(new Vector3f(0f, 1.5f, 4f)); - cam.setRotation(new Quaternion(0.003f, 0.98271f, -0.1846f, 0.014f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - result.setGravity(Vector3f.ZERO); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2021-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; + +/** + * A simple example of a PhysicsJoint with a motor. + *

+ * Builds upon HelloLimit. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloMotor extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloMotor application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloMotor application = new HelloMotor(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a dynamic, green frame. + PhysicsRigidBody frameBody = addFrame(); + + // Add a dynamic, yellow box for the door. + PhysicsRigidBody doorBody = addDoor(); + + // Add a double-ended physics joint to join the door to the frame. + Vector3f pivotLocation = new Vector3f(-1f, 0f, 0f); + Quaternion pivotOrientation = Quaternion.IDENTITY; + New6Dof joint = New6Dof.newInstance(frameBody, doorBody, + pivotLocation, pivotOrientation, RotationOrder.XYZ); + physicsSpace.addJoint(joint); + + int xRotationDof = 3 + PhysicsSpace.AXIS_X; + int yRotationDof = 3 + PhysicsSpace.AXIS_Y; + int zRotationDof = 3 + PhysicsSpace.AXIS_Z; + + // Lock the X and Z rotation DOFs. + joint.set(MotorParam.LowerLimit, xRotationDof, 0f); + joint.set(MotorParam.LowerLimit, zRotationDof, 0f); + joint.set(MotorParam.UpperLimit, xRotationDof, 0f); + joint.set(MotorParam.UpperLimit, zRotationDof, 0f); + + // Limit the Y rotation DOF. + joint.set(MotorParam.LowerLimit, yRotationDof, 0f); + joint.set(MotorParam.UpperLimit, yRotationDof, 1.2f); + + // Enable the motor for Y rotation. + final RotationMotor motor = joint.getRotationMotor(PhysicsSpace.AXIS_Y); + motor.set(MotorParam.TargetVelocity, 0.4f); + motor.setMotorEnabled(true); + + // Configure the InputManager to respond to the spacebar. + inputManager.addMapping("reverse", new KeyTrigger(KeyInput.KEY_SPACE)); + InputListener actionListener = new ActionListener() { + @Override + public void onAction(String action, boolean ongoing, float tpf) { + if (action.equals("reverse") && ongoing) { + // Reverse the motor's direction. + float rate = motor.get(MotorParam.TargetVelocity); + motor.set(MotorParam.TargetVelocity, -rate); + } + } + }; + inputManager.addListener(actionListener, "reverse"); + } + // ************************************************************************* + // private methods + + /** + * Create a dynamic rigid body with a box shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addDoor() { + BoxCollisionShape shape = new BoxCollisionShape(0.8f, 0.8f, 0.1f); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(result); + + // Disable sleep (deactivation). + result.setEnableSleep(false); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + + return result; + } + + /** + * Create a dynamic body with a square-frame shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addFrame() { + CapsuleCollisionShape xShape + = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_X); + CapsuleCollisionShape yShape + = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_Y); + + CompoundCollisionShape frameShape = new CompoundCollisionShape(); + frameShape.addChildShape(xShape, 0f, +1f, 0f); + frameShape.addChildShape(xShape, 0f, -1f, 0f); + frameShape.addChildShape(yShape, +1f, 0f, 0f); + frameShape.addChildShape(yShape, -1f, 0f, 0f); + + PhysicsRigidBody result = new PhysicsRigidBody(frameShape); + physicsSpace.addCollisionObject(result); + + Material greenMaterial = createLitMaterial(0f, 1f, 0f); + result.setDebugMaterial(greenMaterial); + + return result; + } + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Position the camera during startup. + */ + private void configureCamera() { + flyCam.setMoveSpeed(5f); + + cam.setLocation(new Vector3f(0f, 1.5f, 4f)); + cam.setRotation(new Quaternion(0.003f, 0.98271f, -0.1846f, 0.014f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + result.setGravity(Vector3f.ZERO); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNewHinge.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNewHinge.java index b449b6248..ebdf076d6 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNewHinge.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNewHinge.java @@ -1,284 +1,284 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CylinderCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.joints.NewHinge; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.FastMath; -import com.jme3.math.Plane; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.texture.Texture; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import jme3utilities.math.MyQuaternion; - -/** - * An example of vehicle physics using NewHinge. - *

- * Builds upon HelloVehicle. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloNewHinge - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - /** - * wheels for steering - */ - final private static List steer = new ArrayList<>(2); - /** - * drive wheels - */ - final private static List drive = new ArrayList<>(2); - private static PhysicsRigidBody chassis; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloNewHinge application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloNewHinge application = new HelloNewHinge(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - physicsSpace = configurePhysics(); - - // Create a wedge-shaped vehicle with a low center of gravity. - // The local forward direction is +Z. - float noseZ = 1.4f; // offset from chassis center - float spoilerY = 0.5f; // offset from chassis center - float tailZ = -0.7f; // offset from chassis center - float undercarriageY = -0.1f; // offset from chassis center - float halfWidth = 0.4f; - Collection cornerLocations = new ArrayList<>(6); - cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, noseZ)); - cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, noseZ)); - cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, tailZ)); - cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, tailZ)); - cornerLocations.add(new Vector3f(+halfWidth, spoilerY, tailZ)); - cornerLocations.add(new Vector3f(-halfWidth, spoilerY, tailZ)); - HullCollisionShape wedgeShape - = new HullCollisionShape(cornerLocations); - float mass = 5f; - chassis = new PhysicsRigidBody(wedgeShape, mass); - chassis.setEnableSleep(false); - physicsSpace.addCollisionObject(chassis); - - // Add 4 wheels, 2 in the front (for steering) and 2 in the rear. - boolean front = true; - boolean rear = false; - float frontAxisZ = 0.7f * noseZ; // offset from chassis center - float rearAxisZ = 0.8f * tailZ; // offset from chassis center - float radius = 0.3f; // of each tire - float restLength = 0.2f; // of the suspension - float xOffset = 0.9f * halfWidth; - Vector3f axleDirection = new Vector3f(-1f, 0f, 0f); - Vector3f suspensionDirection = new Vector3f(0f, -1f, 0f); - addWheel(new Vector3f(-xOffset, 0f, frontAxisZ), - suspensionDirection, axleDirection, restLength, radius, front); - addWheel(new Vector3f(xOffset, 0f, frontAxisZ), - suspensionDirection, axleDirection, restLength, radius, front); - addWheel(new Vector3f(-xOffset, 0f, rearAxisZ), - suspensionDirection, axleDirection, restLength, radius, rear); - addWheel(new Vector3f(xOffset, 0f, rearAxisZ), - suspensionDirection, axleDirection, restLength, radius, rear); - - // Apply a steering angle of 6 degrees left (to the front wheels). - for (RotationMotor motor : steer) { - motor.set(MotorParam.ServoTarget, FastMath.PI / 30f); - } - - // Add a static plane to represent the ground. - float y = -radius - 0.35f; - addPlane(y); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Apply a constant torque (to the rear wheels). - for (PhysicsRigidBody wheel : drive) { - Vector3f torque = new Vector3f(1f, 0f, 0f); - MyQuaternion.rotate(wheel.getPhysicsRotation(), torque, torque); - wheel.applyTorque(torque); - } - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Add a horizontal plane body to the space. - * - * @param y (the desired elevation, in physics-space coordinates) - */ - private void addPlane(float y) { - Plane plane = new Plane(Vector3f.UNIT_Y, y); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - // Load a repeating tile texture. - String assetPath = "Textures/greenTile.png"; - boolean flipY = false; - TextureKey key = new TextureKey(assetPath, flipY); - boolean generateMips = true; - key.setGenerateMips(generateMips); - Texture texture = assetManager.loadTexture(key); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - - // Enable anisotropic filtering, to reduce blurring. - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - texture.setAnisotropicFilter(degree); - - // Apply a tiled, unshaded debug material to the body. - Material material = new Material(assetManager, Materials.UNSHADED); - material.setTexture("ColorMap", texture); - body.setDebugMaterial(material); - - // Generate texture coordinates during debug-mesh initialization. - float tileSize = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); - body.setDebugMeshInitListener(planeDmiListener); - - physicsSpace.addCollisionObject(body); - } - - /** - * Add a cylindrical wheel, joined to the chassis by a NewHinge. - * - * @param connectionPoint the location of the connection point (not null) - * @param suspensionDirection the direction of suspension motion (not null, - * not zero, unaffected) - * @param axle the direction of the axle's axis (not null, not zero, - * unaffected) - * @param restLength the rest length - * @param wheelRadius the desired radius of the wheel - * @param isFrontWheel true for a front/steer wheel, false for a rear/drive - * wheel - */ - private void addWheel(Vector3f connectionPoint, - Vector3f suspensionDirection, Vector3f axle, float restLength, - float wheelRadius, boolean isFrontWheel) { - float thickness = 0.5f * wheelRadius; - CylinderCollisionShape shape = new CylinderCollisionShape( - wheelRadius, thickness, PhysicsSpace.AXIS_X); - float mass = 0.5f; - PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); - body.setEnableSleep(false); - Vector3f center = connectionPoint.add(0f, -restLength, 0f); - body.setPhysicsLocation(center); - physicsSpace.addCollisionObject(body); - - NewHinge joint = new NewHinge( - chassis, body, center, suspensionDirection, axle); - if (isFrontWheel) { - RotationMotor motor = joint.getRotationMotor(PhysicsSpace.AXIS_Z); - motor.setMotorEnabled(true); - motor.setServoEnabled(true); - motor.set(MotorParam.TargetVelocity, 1f); - steer.add(motor); - } else { - joint.set(MotorParam.LowerLimit, 3 + PhysicsSpace.AXIS_Z, 0f); - joint.set(MotorParam.UpperLimit, 3 + PhysicsSpace.AXIS_Z, 0f); - drive.add(body); - } - joint.set(MotorParam.Damping, PhysicsSpace.AXIS_Z, 30f); - joint.set(MotorParam.Stiffness, PhysicsSpace.AXIS_Z, 90f); - joint.setCollisionBetweenLinkedBodies(false); - physicsSpace.addJoint(joint); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CylinderCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.joints.NewHinge; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.texture.Texture; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import jme3utilities.math.MyQuaternion; + +/** + * An example of vehicle physics using NewHinge. + *

+ * Builds upon HelloVehicle. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloNewHinge + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + /** + * wheels for steering + */ + final private static List steer = new ArrayList<>(2); + /** + * drive wheels + */ + final private static List drive = new ArrayList<>(2); + private static PhysicsRigidBody chassis; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloNewHinge application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloNewHinge application = new HelloNewHinge(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + physicsSpace = configurePhysics(); + + // Create a wedge-shaped vehicle with a low center of gravity. + // The local forward direction is +Z. + float noseZ = 1.4f; // offset from chassis center + float spoilerY = 0.5f; // offset from chassis center + float tailZ = -0.7f; // offset from chassis center + float undercarriageY = -0.1f; // offset from chassis center + float halfWidth = 0.4f; + Collection cornerLocations = new ArrayList<>(6); + cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, noseZ)); + cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, noseZ)); + cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, tailZ)); + cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, tailZ)); + cornerLocations.add(new Vector3f(+halfWidth, spoilerY, tailZ)); + cornerLocations.add(new Vector3f(-halfWidth, spoilerY, tailZ)); + HullCollisionShape wedgeShape + = new HullCollisionShape(cornerLocations); + float mass = 5f; + chassis = new PhysicsRigidBody(wedgeShape, mass); + chassis.setEnableSleep(false); + physicsSpace.addCollisionObject(chassis); + + // Add 4 wheels, 2 in the front (for steering) and 2 in the rear. + boolean front = true; + boolean rear = false; + float frontAxisZ = 0.7f * noseZ; // offset from chassis center + float rearAxisZ = 0.8f * tailZ; // offset from chassis center + float radius = 0.3f; // of each tire + float restLength = 0.2f; // of the suspension + float xOffset = 0.9f * halfWidth; + Vector3f axleDirection = new Vector3f(-1f, 0f, 0f); + Vector3f suspensionDirection = new Vector3f(0f, -1f, 0f); + addWheel(new Vector3f(-xOffset, 0f, frontAxisZ), + suspensionDirection, axleDirection, restLength, radius, front); + addWheel(new Vector3f(xOffset, 0f, frontAxisZ), + suspensionDirection, axleDirection, restLength, radius, front); + addWheel(new Vector3f(-xOffset, 0f, rearAxisZ), + suspensionDirection, axleDirection, restLength, radius, rear); + addWheel(new Vector3f(xOffset, 0f, rearAxisZ), + suspensionDirection, axleDirection, restLength, radius, rear); + + // Apply a steering angle of 6 degrees left (to the front wheels). + for (RotationMotor motor : steer) { + motor.set(MotorParam.ServoTarget, FastMath.PI / 30f); + } + + // Add a static plane to represent the ground. + float y = -radius - 0.35f; + addPlane(y); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Apply a constant torque (to the rear wheels). + for (PhysicsRigidBody wheel : drive) { + Vector3f torque = new Vector3f(1f, 0f, 0f); + MyQuaternion.rotate(wheel.getPhysicsRotation(), torque, torque); + wheel.applyTorque(torque); + } + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Add a horizontal plane body to the space. + * + * @param y (the desired elevation, in physics-space coordinates) + */ + private void addPlane(float y) { + Plane plane = new Plane(Vector3f.UNIT_Y, y); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + // Load a repeating tile texture. + String assetPath = "Textures/greenTile.png"; + boolean flipY = false; + TextureKey key = new TextureKey(assetPath, flipY); + boolean generateMips = true; + key.setGenerateMips(generateMips); + Texture texture = assetManager.loadTexture(key); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + + // Enable anisotropic filtering, to reduce blurring. + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + texture.setAnisotropicFilter(degree); + + // Apply a tiled, unshaded debug material to the body. + Material material = new Material(assetManager, Materials.UNSHADED); + material.setTexture("ColorMap", texture); + body.setDebugMaterial(material); + + // Generate texture coordinates during debug-mesh initialization. + float tileSize = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); + body.setDebugMeshInitListener(planeDmiListener); + + physicsSpace.addCollisionObject(body); + } + + /** + * Add a cylindrical wheel, joined to the chassis by a NewHinge. + * + * @param connectionPoint the location of the connection point (not null) + * @param suspensionDirection the direction of suspension motion (not null, + * not zero, unaffected) + * @param axle the direction of the axle's axis (not null, not zero, + * unaffected) + * @param restLength the rest length + * @param wheelRadius the desired radius of the wheel + * @param isFrontWheel true for a front/steer wheel, false for a rear/drive + * wheel + */ + private void addWheel(Vector3f connectionPoint, + Vector3f suspensionDirection, Vector3f axle, float restLength, + float wheelRadius, boolean isFrontWheel) { + float thickness = 0.5f * wheelRadius; + CylinderCollisionShape shape = new CylinderCollisionShape( + wheelRadius, thickness, PhysicsSpace.AXIS_X); + float mass = 0.5f; + PhysicsRigidBody body = new PhysicsRigidBody(shape, mass); + body.setEnableSleep(false); + Vector3f center = connectionPoint.add(0f, -restLength, 0f); + body.setPhysicsLocation(center); + physicsSpace.addCollisionObject(body); + + NewHinge joint = new NewHinge( + chassis, body, center, suspensionDirection, axle); + if (isFrontWheel) { + RotationMotor motor = joint.getRotationMotor(PhysicsSpace.AXIS_Z); + motor.setMotorEnabled(true); + motor.setServoEnabled(true); + motor.set(MotorParam.TargetVelocity, 1f); + steer.add(motor); + } else { + joint.set(MotorParam.LowerLimit, 3 + PhysicsSpace.AXIS_Z, 0f); + joint.set(MotorParam.UpperLimit, 3 + PhysicsSpace.AXIS_Z, 0f); + drive.add(body); + } + joint.set(MotorParam.Damping, PhysicsSpace.AXIS_Z, 30f); + joint.set(MotorParam.Stiffness, PhysicsSpace.AXIS_Z, 90f); + joint.setCollisionBetweenLinkedBodies(false); + physicsSpace.addJoint(joint); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNonUniformGravity.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNonUniformGravity.java index 7163c5988..1ae95d01a 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNonUniformGravity.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloNonUniformGravity.java @@ -1,153 +1,153 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; -import jme3utilities.debug.AxesVisualizer; -import jme3utilities.minie.FilterAll; - -/** - * A simple example of non-uniform gravity. - *

- * Builds upon HelloRigidBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloNonUniformGravity - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - /** - * dynamic body subjected to non-uniform gravity - */ - private static PhysicsRigidBody planet; - /** - * temporary storage for vectors - */ - final private static Vector3f tmpVector = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloNonUniformGravity application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloNonUniformGravity application = new HelloNonUniformGravity(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - physicsSpace.addTickListener(this); - - // Reduce the time step for better accuracy. - physicsSpace.setAccuracy(0.005f); - /* - * Enable debug visualization - * (including gravity-vector visualization) - * to reveal what occurs in physics space. - */ - bulletAppState.setDebugEnabled(true); - bulletAppState.setDebugGravityVectorFilter(new FilterAll(true)); - - // Create a CollisionShape for the planet. - float planetRadius = 0.1f; - CollisionShape planetShape = new SphereCollisionShape(planetRadius); - - // Create a planet (dynamic rigid body) and add it to the space. - float planetMass = 1f; // physics mass unit = 10^25 kg - planet = new PhysicsRigidBody(planetShape, planetMass); - physicsSpace.addCollisionObject(planet); - - // Prevent deactivation of the planet. - planet.setEnableSleep(false); - - // Kick the planet into orbit around the central black hole. - planet.setPhysicsLocation(new Vector3f(2f, 0f, 0f)); - planet.applyCentralImpulse(new Vector3f(0f, -1f, 0f)); - - // Visualize axes to indicate the black hole's location. - float axisLength = 1f; - AxesVisualizer axes = new AxesVisualizer(assetManager, axisLength); - axes.setLineWidth(AxesVisualizer.widthForSolid); - rootNode.addControl(axes); - axes.setEnabled(true); - - // Minie's BulletAppState simulates the dynamics... - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Calculate the gravitational acceleration GM/r^2. - planet.getPhysicsLocation(tmpVector); - float r2 = tmpVector.lengthSquared(); //squared distance from black hole - tmpVector.normalizeLocal(); - tmpVector.multLocal(-3f / r2); - planet.setGravity(tmpVector); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; +import jme3utilities.debug.AxesVisualizer; +import jme3utilities.minie.FilterAll; + +/** + * A simple example of non-uniform gravity. + *

+ * Builds upon HelloRigidBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloNonUniformGravity + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + /** + * dynamic body subjected to non-uniform gravity + */ + private static PhysicsRigidBody planet; + /** + * temporary storage for vectors + */ + final private static Vector3f tmpVector = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloNonUniformGravity application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloNonUniformGravity application = new HelloNonUniformGravity(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + physicsSpace.addTickListener(this); + + // Reduce the time step for better accuracy. + physicsSpace.setAccuracy(0.005f); + /* + * Enable debug visualization + * (including gravity-vector visualization) + * to reveal what occurs in physics space. + */ + bulletAppState.setDebugEnabled(true); + bulletAppState.setDebugGravityVectorFilter(new FilterAll(true)); + + // Create a CollisionShape for the planet. + float planetRadius = 0.1f; + CollisionShape planetShape = new SphereCollisionShape(planetRadius); + + // Create a planet (dynamic rigid body) and add it to the space. + float planetMass = 1f; // physics mass unit = 10^25 kg + planet = new PhysicsRigidBody(planetShape, planetMass); + physicsSpace.addCollisionObject(planet); + + // Prevent deactivation of the planet. + planet.setEnableSleep(false); + + // Kick the planet into orbit around the central black hole. + planet.setPhysicsLocation(new Vector3f(2f, 0f, 0f)); + planet.applyCentralImpulse(new Vector3f(0f, -1f, 0f)); + + // Visualize axes to indicate the black hole's location. + float axisLength = 1f; + AxesVisualizer axes = new AxesVisualizer(assetManager, axisLength); + axes.setLineWidth(AxesVisualizer.widthForSolid); + rootNode.addControl(axes); + axes.setEnabled(true); + + // Minie's BulletAppState simulates the dynamics... + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Calculate the gravitational acceleration GM/r^2. + planet.getPhysicsLocation(tmpVector); + float r2 = tmpVector.lengthSquared(); //squared distance from black hole + tmpVector.normalizeLocal(); + tmpVector.multLocal(-3f / r2); + planet.setGravity(tmpVector); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPin.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPin.java index 4b913695a..38d7f364e 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPin.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPin.java @@ -1,115 +1,115 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import jme3utilities.mesh.ClothGrid; - -/** - * A simple cloth simulation with a pinned node. - *

- * Builds upon HelloCloth. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloPin extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloPin application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloPin application = new HelloPin(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - // Relocate the camera. - cam.setLocation(new Vector3f(0f, 1f, 8f)); - - // Create a static, rigid sphere and add it to the physics space. - float radius = 1f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - PhysicsRigidBody sphere - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(sphere); - - // Generate a subdivided square mesh with alternating diagonals. - int numLines = 41; - float lineSpacing = 0.1f; // mesh units - Mesh squareGrid = new ClothGrid(numLines, numLines, lineSpacing); - - // Create a soft square and add it to the physics space. - PhysicsSoftBody cloth = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(squareGrid, cloth); - physicsSpace.addCollisionObject(cloth); - - // Pin one of the corner nodes by setting its mass to zero. - int nodeIndex = 0; // upper left corner - cloth.setNodeMass(nodeIndex, PhysicsBody.massForStatic); - /* - * Make the cloth flexible by reducing the angular stiffness - * of its material. - */ - SoftBodyMaterial mat = cloth.getSoftMaterial(); - mat.setAngularStiffness(0f); // default=1 - - // Improve simulation accuracy by increasing - // the number of position-solver iterations for the cloth. - SoftBodyConfig config = cloth.getSoftConfig(); - config.setPositionIterations(9); // default=1 - - // Translate the cloth upward to its starting location. - cloth.applyTranslation(new Vector3f(0f, 2f, 0f)); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import jme3utilities.mesh.ClothGrid; + +/** + * A simple cloth simulation with a pinned node. + *

+ * Builds upon HelloCloth. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloPin extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloPin application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloPin application = new HelloPin(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + // Relocate the camera. + cam.setLocation(new Vector3f(0f, 1f, 8f)); + + // Create a static, rigid sphere and add it to the physics space. + float radius = 1f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + PhysicsRigidBody sphere + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(sphere); + + // Generate a subdivided square mesh with alternating diagonals. + int numLines = 41; + float lineSpacing = 0.1f; // mesh units + Mesh squareGrid = new ClothGrid(numLines, numLines, lineSpacing); + + // Create a soft square and add it to the physics space. + PhysicsSoftBody cloth = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(squareGrid, cloth); + physicsSpace.addCollisionObject(cloth); + + // Pin one of the corner nodes by setting its mass to zero. + int nodeIndex = 0; // upper left corner + cloth.setNodeMass(nodeIndex, PhysicsBody.massForStatic); + /* + * Make the cloth flexible by reducing the angular stiffness + * of its material. + */ + SoftBodyMaterial mat = cloth.getSoftMaterial(); + mat.setAngularStiffness(0f); // default=1 + + // Improve simulation accuracy by increasing + // the number of position-solver iterations for the cloth. + SoftBodyConfig config = cloth.getSoftConfig(); + config.setPositionIterations(9); // default=1 + + // Translate the cloth upward to its starting location. + cloth.applyTranslation(new Vector3f(0f, 2f, 0f)); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPoi.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPoi.java index 3a3e9aa56..7fc0f1d41 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPoi.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloPoi.java @@ -1,443 +1,443 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.PhysicsRayTestResult; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.terrain.heightmap.HeightMap; -import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import java.util.List; -import jme3utilities.MeshNormals; -import jme3utilities.debug.PointVisualizer; -import jme3utilities.math.MyVector3f; - -/** - * A simple example of point-of-impact prediction. - *

- * Press the spacebar or LMB to launch a missile. - *

- * Builds upon HelloWalk. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloPoi - extends SimpleApplication - implements ActionListener, PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * launch speed for missiles (in physics-space units per second) - */ - final private static float launchSpeed = 15f; - // ************************************************************************* - // fields - - /** - * true when a launch has been requested, but it hasn't occurred yet - */ - private static boolean launchRequested = false; - /** - * Material to visualize missiles - */ - private static Material redMaterial; - /** - * body to model the terrain - */ - private static PhysicsRigidBody terrain; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - /** - * visualize the predicted point-of-impact - */ - private static PointVisualizer poiIndicator; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloPoi application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloPoi application = new HelloPoi(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - configureInput(); - physicsSpace = configurePhysics(); - - redMaterial = new Material(assetManager, Materials.UNSHADED); - redMaterial.setColor("Color", ColorRGBA.Red.clone()); - - // Add an indicator for the predicted point of impact. - int indicatorSize = 15; // in pixels - poiIndicator = new PointVisualizer( - assetManager, indicatorSize, ColorRGBA.Yellow, "cross"); - rootNode.attachChild(poiIndicator); - poiIndicator.setDepthTest(true); - - // Add a static heightmap to represent the ground. - addTerrain(); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - updateIndicator(); - } - // ************************************************************************* - // ActionListener methods - - /** - * Callback to handle keyboard/button input events. - * - * @param action the name of the input event - * @param ongoing true → pressed, false → released - * @param tpf the time per frame (in seconds, ≥0) - */ - @Override - public void onAction(String action, boolean ongoing, float tpf) { - switch (action) { - case "launch": - if (ongoing) { - launchRequested = true; - } - return; - - default: - System.out.println("Unknown action: " + action); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - if (launchRequested) { - launchRequested = false; - launchMissile(); - } - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.4f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Add a heightfield body to the space. - */ - private void addTerrain() { - // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar - String assetPath = "Textures/Terrain/splat/mountains512.png"; - Texture texture = assetManager.loadTexture(assetPath); - Image image = texture.getImage(); - HeightMap heightMap = new ImageBasedHeightMap(image); - heightMap.setHeightScale(0.2f); - - // Construct a static rigid body based on the HeightMap. - CollisionShape shape = new HeightfieldCollisionShape(heightMap); - terrain = new RigidBodyControl(shape, PhysicsBody.massForStatic); - - physicsSpace.addCollisionObject(terrain); - - // Customize its debug visualization. - Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); - terrain.setDebugMaterial(greenMaterial); - terrain.setDebugMeshNormals(MeshNormals.Smooth); - } - - /** - * Configure the Camera during startup. - */ - private void configureCamera() { - flyCam.setMoveSpeed(10f); - flyCam.setZoomSpeed(10f); - - cam.setLocation(new Vector3f(8f, 30f, -44f)); - cam.setRotation(new Quaternion(0f, 1f, 0f, 0f)); - - float frustumWidth = cam.getFrustumTop() - cam.getFrustumBottom(); - float frustumHeight = cam.getFrustumRight() - cam.getFrustumLeft(); - float aspectRatio = frustumHeight / frustumWidth; - float far = cam.getFrustumFar(); - float fieldOfViewDegrees = 100f; // fish-eye view - - // Bring the near plane closer to reduce clipping. - float near = 0.1f; // default = 1 - cam.setFrustumPerspective(fieldOfViewDegrees, aspectRatio, near, far); - } - - /** - * Configure keyboard/button input during startup. - */ - private void configureInput() { - inputManager.addMapping("launch", - new KeyTrigger(KeyInput.KEY_SPACE), - new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addListener(this, "launch"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } - - /** - * Launch a red sphere into the PhysicsSpace. - */ - private void launchMissile() { - float radius = 0.5f; - CollisionShape missileShape = new SphereCollisionShape(radius); - float mass = 1f; - PhysicsRigidBody missile = new PhysicsRigidBody(missileShape, mass); - physicsSpace.addCollisionObject(missile); - - missile.setCcdMotionThreshold(radius); - missile.setCcdSweptSphereRadius(radius); - missile.setDebugMaterial(redMaterial); - - Vector3f velocity = cam.getDirection().mult(launchSpeed); - missile.setLinearVelocity(velocity); - - Vector3f location = cam.getLocation(); - missile.setPhysicsLocation(location); - } - - /** - * Predict the point of impact for a hypothetical missile. - * - * @param launchLocation the missile's launch location (in physics-space - * coordinates, not null, unaffected) - * @param launchVelocity the missile's launch velocity (in physics-space - * coordinates, not null, unaffected) - * @return a new location vector (in physics-space coordinates) or null for - * no prediction - */ - private Vector3f predictPoi( - Vector3f launchLocation, Vector3f launchVelocity) { - Vector3f gravity = physicsSpace.getGravity(null); - Vector3f velocity = launchVelocity.clone(); - Vector3f location = launchLocation.clone(); - Vector3f previousLocation = new Vector3f(); - final float timeStep = 0.02f; // seconds per step - - for (int stepIndex = 0; stepIndex < 150; ++stepIndex) { - previousLocation.set(location); - MyVector3f.accumulateScaled(location, velocity, timeStep); - MyVector3f.accumulateScaled(velocity, gravity, timeStep); - List rayTest - = physicsSpace.rayTestRaw(previousLocation, location); - - // Find the closest contact with the terrain. - float closestFraction = 9f; - for (PhysicsRayTestResult hit : rayTest) { - if (hit.getCollisionObject() == terrain) { - // ignore other missiles! - float hitFraction = hit.getHitFraction(); - if (hitFraction < closestFraction) { - closestFraction = hitFraction; - } - } - } - - if (closestFraction <= 1f) { - Vector3f result = MyVector3f.lerp( - closestFraction, previousLocation, location, null); - return result; - } - } - - // The predicted impact is >3 seconds away. - return null; - } - - /** - * Update the POI indicator. - */ - private void updateIndicator() { - // Predict the point-of-impact for a hypothetical missile - // launched along the camera's line of sight. - Vector3f launchLocation = cam.getLocation(); - Vector3f launchVelocity = cam.getDirection().mult(launchSpeed); - Vector3f predictedLocation = predictPoi(launchLocation, launchVelocity); - - // Update the POI indicator. - if (predictedLocation == null) { - poiIndicator.setEnabled(false); - } else { - poiIndicator.setEnabled(true); - poiIndicator.setLocalTranslation(predictedLocation); - } - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.PhysicsRayTestResult; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import java.util.List; +import jme3utilities.MeshNormals; +import jme3utilities.debug.PointVisualizer; +import jme3utilities.math.MyVector3f; + +/** + * A simple example of point-of-impact prediction. + *

+ * Press the spacebar or LMB to launch a missile. + *

+ * Builds upon HelloWalk. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloPoi + extends SimpleApplication + implements ActionListener, PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * launch speed for missiles (in physics-space units per second) + */ + final private static float launchSpeed = 15f; + // ************************************************************************* + // fields + + /** + * true when a launch has been requested, but it hasn't occurred yet + */ + private static boolean launchRequested = false; + /** + * Material to visualize missiles + */ + private static Material redMaterial; + /** + * body to model the terrain + */ + private static PhysicsRigidBody terrain; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + /** + * visualize the predicted point-of-impact + */ + private static PointVisualizer poiIndicator; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloPoi application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloPoi application = new HelloPoi(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + configureInput(); + physicsSpace = configurePhysics(); + + redMaterial = new Material(assetManager, Materials.UNSHADED); + redMaterial.setColor("Color", ColorRGBA.Red.clone()); + + // Add an indicator for the predicted point of impact. + int indicatorSize = 15; // in pixels + poiIndicator = new PointVisualizer( + assetManager, indicatorSize, ColorRGBA.Yellow, "cross"); + rootNode.attachChild(poiIndicator); + poiIndicator.setDepthTest(true); + + // Add a static heightmap to represent the ground. + addTerrain(); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + updateIndicator(); + } + // ************************************************************************* + // ActionListener methods + + /** + * Callback to handle keyboard/button input events. + * + * @param action the name of the input event + * @param ongoing true → pressed, false → released + * @param tpf the time per frame (in seconds, ≥0) + */ + @Override + public void onAction(String action, boolean ongoing, float tpf) { + switch (action) { + case "launch": + if (ongoing) { + launchRequested = true; + } + return; + + default: + System.out.println("Unknown action: " + action); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + if (launchRequested) { + launchRequested = false; + launchMissile(); + } + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.4f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Add a heightfield body to the space. + */ + private void addTerrain() { + // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar + String assetPath = "Textures/Terrain/splat/mountains512.png"; + Texture texture = assetManager.loadTexture(assetPath); + Image image = texture.getImage(); + HeightMap heightMap = new ImageBasedHeightMap(image); + heightMap.setHeightScale(0.2f); + + // Construct a static rigid body based on the HeightMap. + CollisionShape shape = new HeightfieldCollisionShape(heightMap); + terrain = new RigidBodyControl(shape, PhysicsBody.massForStatic); + + physicsSpace.addCollisionObject(terrain); + + // Customize its debug visualization. + Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); + terrain.setDebugMaterial(greenMaterial); + terrain.setDebugMeshNormals(MeshNormals.Smooth); + } + + /** + * Configure the Camera during startup. + */ + private void configureCamera() { + flyCam.setMoveSpeed(10f); + flyCam.setZoomSpeed(10f); + + cam.setLocation(new Vector3f(8f, 30f, -44f)); + cam.setRotation(new Quaternion(0f, 1f, 0f, 0f)); + + float frustumWidth = cam.getFrustumTop() - cam.getFrustumBottom(); + float frustumHeight = cam.getFrustumRight() - cam.getFrustumLeft(); + float aspectRatio = frustumHeight / frustumWidth; + float far = cam.getFrustumFar(); + float fieldOfViewDegrees = 100f; // fish-eye view + + // Bring the near plane closer to reduce clipping. + float near = 0.1f; // default = 1 + cam.setFrustumPerspective(fieldOfViewDegrees, aspectRatio, near, far); + } + + /** + * Configure keyboard/button input during startup. + */ + private void configureInput() { + inputManager.addMapping("launch", + new KeyTrigger(KeyInput.KEY_SPACE), + new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "launch"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } + + /** + * Launch a red sphere into the PhysicsSpace. + */ + private void launchMissile() { + float radius = 0.5f; + CollisionShape missileShape = new SphereCollisionShape(radius); + float mass = 1f; + PhysicsRigidBody missile = new PhysicsRigidBody(missileShape, mass); + physicsSpace.addCollisionObject(missile); + + missile.setCcdMotionThreshold(radius); + missile.setCcdSweptSphereRadius(radius); + missile.setDebugMaterial(redMaterial); + + Vector3f velocity = cam.getDirection().mult(launchSpeed); + missile.setLinearVelocity(velocity); + + Vector3f location = cam.getLocation(); + missile.setPhysicsLocation(location); + } + + /** + * Predict the point of impact for a hypothetical missile. + * + * @param launchLocation the missile's launch location (in physics-space + * coordinates, not null, unaffected) + * @param launchVelocity the missile's launch velocity (in physics-space + * coordinates, not null, unaffected) + * @return a new location vector (in physics-space coordinates) or null for + * no prediction + */ + private Vector3f predictPoi( + Vector3f launchLocation, Vector3f launchVelocity) { + Vector3f gravity = physicsSpace.getGravity(null); + Vector3f velocity = launchVelocity.clone(); + Vector3f location = launchLocation.clone(); + Vector3f previousLocation = new Vector3f(); + final float timeStep = 0.02f; // seconds per step + + for (int stepIndex = 0; stepIndex < 150; ++stepIndex) { + previousLocation.set(location); + MyVector3f.accumulateScaled(location, velocity, timeStep); + MyVector3f.accumulateScaled(velocity, gravity, timeStep); + List rayTest + = physicsSpace.rayTestRaw(previousLocation, location); + + // Find the closest contact with the terrain. + float closestFraction = 9f; + for (PhysicsRayTestResult hit : rayTest) { + if (hit.getCollisionObject() == terrain) { + // ignore other missiles! + float hitFraction = hit.getHitFraction(); + if (hitFraction < closestFraction) { + closestFraction = hitFraction; + } + } + } + + if (closestFraction <= 1f) { + Vector3f result = MyVector3f.lerp( + closestFraction, previousLocation, location, null); + return result; + } + } + + // The predicted impact is >3 seconds away. + return null; + } + + /** + * Update the POI indicator. + */ + private void updateIndicator() { + // Predict the point-of-impact for a hypothetical missile + // launched along the camera's line of sight. + Vector3f launchLocation = cam.getLocation(); + Vector3f launchVelocity = cam.getDirection().mult(launchSpeed); + Vector3f predictedLocation = predictPoi(launchLocation, launchVelocity); + + // Update the POI indicator. + if (predictedLocation == null) { + poiIndicator.setEnabled(false); + } else { + poiIndicator.setEnabled(true); + poiIndicator.setLocalTranslation(predictedLocation); + } + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRbc.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRbc.java index c8a66ae0d..606431e1a 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRbc.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRbc.java @@ -1,156 +1,156 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Sphere; -import com.jme3.system.AppSettings; - -/** - * A simple example using RigidBodyControl. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloRbc extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloRbc application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloRbc application = new HelloRbc(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - PhysicsSpace physicsSpace = configurePhysics(); - - // Create a material and a mesh for balls. - float ballRadius = 1f; - Material ballMaterial = new Material(assetManager, Materials.LIGHTING); - Mesh ballMesh = new Sphere(16, 32, ballRadius); - - // Create geometries for a dynamic ball and a static ball - // and add them to the scene graph. - Geometry dyna = new Geometry("dyna", ballMesh); - dyna.setMaterial(ballMaterial); - rootNode.attachChild(dyna); - - Geometry stat = new Geometry("stat", ballMesh); - stat.setMaterial(ballMaterial); - rootNode.attachChild(stat); - - // Create RBCs for both balls and add them to the geometries. - float mass = 2f; - RigidBodyControl dynaRbc = new RigidBodyControl(mass); - dyna.addControl(dynaRbc); - - RigidBodyControl statRbc - = new RigidBodyControl(PhysicsBody.massForStatic); - stat.addControl(statRbc); - - // Add the controls to the physics space. - dynaRbc.setPhysicsSpace(physicsSpace); - statRbc.setPhysicsSpace(physicsSpace); - - // Position the balls in physics space. - dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - statRbc.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); - - // Add lighting. - addLighting(rootNode); - - // Minie's BulletAppState simulates the dynamics... - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private static void addLighting(Spatial scene) { - // Light the scene with ambient and directional lights. - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * A simple example using RigidBodyControl. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloRbc extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloRbc application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloRbc application = new HelloRbc(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + PhysicsSpace physicsSpace = configurePhysics(); + + // Create a material and a mesh for balls. + float ballRadius = 1f; + Material ballMaterial = new Material(assetManager, Materials.LIGHTING); + Mesh ballMesh = new Sphere(16, 32, ballRadius); + + // Create geometries for a dynamic ball and a static ball + // and add them to the scene graph. + Geometry dyna = new Geometry("dyna", ballMesh); + dyna.setMaterial(ballMaterial); + rootNode.attachChild(dyna); + + Geometry stat = new Geometry("stat", ballMesh); + stat.setMaterial(ballMaterial); + rootNode.attachChild(stat); + + // Create RBCs for both balls and add them to the geometries. + float mass = 2f; + RigidBodyControl dynaRbc = new RigidBodyControl(mass); + dyna.addControl(dynaRbc); + + RigidBodyControl statRbc + = new RigidBodyControl(PhysicsBody.massForStatic); + stat.addControl(statRbc); + + // Add the controls to the physics space. + dynaRbc.setPhysicsSpace(physicsSpace); + statRbc.setPhysicsSpace(physicsSpace); + + // Position the balls in physics space. + dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + statRbc.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); + + // Add lighting. + addLighting(rootNode); + + // Minie's BulletAppState simulates the dynamics... + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private static void addLighting(Spatial scene) { + // Light the scene with ambient and directional lights. + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRigidBody.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRigidBody.java index 510fdc721..efabce8b7 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRigidBody.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloRigidBody.java @@ -1,102 +1,102 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example of 2 colliding balls, illustrating the 5 basic features of - * responsive, dynamic, rigid bodies:

    - *
  • rigidity (fixed shape),
  • - *
  • inertia (resistance to changes of motion),
  • - *
  • dynamics (motion determined by forces, torques, and impulses),
  • - *
  • gravity (continual downward force), and
  • - *
  • contact response (avoid intersecting with other bodies).
  • - *
- * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloRigidBody extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloRigidBody application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloRigidBody application = new HelloRigidBody(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // For clarity, simulate at 1/10th normal speed. - bulletAppState.setSpeed(0.1f); - - // Create a CollisionShape for balls. - float ballRadius = 1f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - - // Create 2 balls (dynamic rigid bodies) and add them to the space. - float ballMass = 2f; - PhysicsRigidBody ball1 = new PhysicsRigidBody(ballShape, ballMass); - physicsSpace.addCollisionObject(ball1); - PhysicsRigidBody ball2 = new PhysicsRigidBody(ballShape, ballMass); - physicsSpace.addCollisionObject(ball2); - - // Locate the balls initially 2 PSU (physics-space units) apart. - // In other words, 4 PSU from center to center. - ball1.setPhysicsLocation(new Vector3f(1f, 1f, 0f)); - ball2.setPhysicsLocation(new Vector3f(5f, 1f, 0f)); - - // Set ball #2 on a collision course with ball #1. - ball2.applyCentralImpulse(new Vector3f(-25f, 0f, 0f)); - - // Minie's BulletAppState simulates the dynamics... - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example of 2 colliding balls, illustrating the 5 basic features of + * responsive, dynamic, rigid bodies:
    + *
  • rigidity (fixed shape),
  • + *
  • inertia (resistance to changes of motion),
  • + *
  • dynamics (motion determined by forces, torques, and impulses),
  • + *
  • gravity (continual downward force), and
  • + *
  • contact response (avoid intersecting with other bodies).
  • + *
+ * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloRigidBody extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloRigidBody application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloRigidBody application = new HelloRigidBody(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // For clarity, simulate at 1/10th normal speed. + bulletAppState.setSpeed(0.1f); + + // Create a CollisionShape for balls. + float ballRadius = 1f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + + // Create 2 balls (dynamic rigid bodies) and add them to the space. + float ballMass = 2f; + PhysicsRigidBody ball1 = new PhysicsRigidBody(ballShape, ballMass); + physicsSpace.addCollisionObject(ball1); + PhysicsRigidBody ball2 = new PhysicsRigidBody(ballShape, ballMass); + physicsSpace.addCollisionObject(ball2); + + // Locate the balls initially 2 PSU (physics-space units) apart. + // In other words, 4 PSU from center to center. + ball1.setPhysicsLocation(new Vector3f(1f, 1f, 0f)); + ball2.setPhysicsLocation(new Vector3f(5f, 1f, 0f)); + + // Set ball #2 on a collision course with ball #1. + ball2.applyCentralImpulse(new Vector3f(-25f, 0f, 0f)); + + // Minie's BulletAppState simulates the dynamics... + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloServo.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloServo.java index 67e5e8060..5416b888a 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloServo.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloServo.java @@ -1,312 +1,312 @@ -/* - Copyright (c) 2021-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.joints.motors.RotationMotor; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.InputListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; - -/** - * A simple example of a PhysicsJoint with a servo. - *

- * Builds upon HelloMotor. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloServo extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloServo application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloServo application = new HelloServo(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a dynamic, green frame. - PhysicsRigidBody frameBody = addFrame(); - - // Add a dynamic, yellow box for the door. - PhysicsRigidBody doorBody = addDoor(); - - // Add a double-ended physics joint to join the door to the frame. - Vector3f pivotLocation = new Vector3f(-1f, 0f, 0f); - Quaternion pivotOrientation = Quaternion.IDENTITY; - New6Dof joint = New6Dof.newInstance(frameBody, doorBody, - pivotLocation, pivotOrientation, RotationOrder.XYZ); - physicsSpace.addJoint(joint); - - int xRotationDof = 3 + PhysicsSpace.AXIS_X; - int yRotationDof = 3 + PhysicsSpace.AXIS_Y; - int zRotationDof = 3 + PhysicsSpace.AXIS_Z; - - // Lock the X and Z rotation DOFs. - joint.set(MotorParam.LowerLimit, xRotationDof, 0f); - joint.set(MotorParam.LowerLimit, zRotationDof, 0f); - joint.set(MotorParam.UpperLimit, xRotationDof, 0f); - joint.set(MotorParam.UpperLimit, zRotationDof, 0f); - - // Limit the Y rotation DOF. - joint.set(MotorParam.LowerLimit, yRotationDof, 0f); - joint.set(MotorParam.UpperLimit, yRotationDof, 1.2f); - - // Enable the motor for Y rotation. - final RotationMotor motor = joint.getRotationMotor(PhysicsSpace.AXIS_Y); - motor.set(MotorParam.TargetVelocity, 0.4f); - motor.setMotorEnabled(true); - motor.setServoEnabled(true); - - // Configure the InputManager to respond to the 1/2/3/4 keys. - inputManager.addMapping("pos1", new KeyTrigger(KeyInput.KEY_1)); - inputManager.addMapping("pos2", new KeyTrigger(KeyInput.KEY_2)); - inputManager.addMapping("pos3", new KeyTrigger(KeyInput.KEY_3)); - inputManager.addMapping("pos4", new KeyTrigger(KeyInput.KEY_4)); - InputListener actionListener = new ActionListener() { - @Override - public void onAction(String action, boolean ongoing, float tpf) { - if (!ongoing) { - return; // ignore key release - } - - // Swing the door to the requested position. - switch (action) { - case "pos1": - motor.set(MotorParam.ServoTarget, 1.2f); - break; - case "pos2": - motor.set(MotorParam.ServoTarget, 0.8f); - break; - case "pos3": - motor.set(MotorParam.ServoTarget, 0.4f); - break; - case "pos4": - motor.set(MotorParam.ServoTarget, 0f); - break; - default: - } - } - }; - inputManager.addListener( - actionListener, "pos1", "pos2", "pos3", "pos4"); - } - // ************************************************************************* - // private methods - - /** - * Create a dynamic rigid body with a box shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addDoor() { - BoxCollisionShape shape = new BoxCollisionShape(0.8f, 0.8f, 0.1f); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(result); - - // Disable sleep (deactivation). - result.setEnableSleep(false); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - - return result; - } - - /** - * Create a dynamic body with a square-frame shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addFrame() { - CapsuleCollisionShape xShape - = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_X); - CapsuleCollisionShape yShape - = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_Y); - - CompoundCollisionShape frameShape = new CompoundCollisionShape(); - frameShape.addChildShape(xShape, 0f, +1f, 0f); - frameShape.addChildShape(xShape, 0f, -1f, 0f); - frameShape.addChildShape(yShape, +1f, 0f, 0f); - frameShape.addChildShape(yShape, -1f, 0f, 0f); - - PhysicsRigidBody result = new PhysicsRigidBody(frameShape); - physicsSpace.addCollisionObject(result); - - Material greenMaterial = createLitMaterial(0f, 1f, 0f); - result.setDebugMaterial(greenMaterial); - - return result; - } - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Position the camera during startup. - */ - private void configureCamera() { - flyCam.setMoveSpeed(5f); - - cam.setLocation(new Vector3f(0f, 1.5f, 4f)); - cam.setRotation(new Quaternion(0.003f, 0.98271f, -0.1846f, 0.014f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - result.setGravity(Vector3f.ZERO); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2021-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.joints.motors.RotationMotor; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; + +/** + * A simple example of a PhysicsJoint with a servo. + *

+ * Builds upon HelloMotor. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloServo extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloServo application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloServo application = new HelloServo(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a dynamic, green frame. + PhysicsRigidBody frameBody = addFrame(); + + // Add a dynamic, yellow box for the door. + PhysicsRigidBody doorBody = addDoor(); + + // Add a double-ended physics joint to join the door to the frame. + Vector3f pivotLocation = new Vector3f(-1f, 0f, 0f); + Quaternion pivotOrientation = Quaternion.IDENTITY; + New6Dof joint = New6Dof.newInstance(frameBody, doorBody, + pivotLocation, pivotOrientation, RotationOrder.XYZ); + physicsSpace.addJoint(joint); + + int xRotationDof = 3 + PhysicsSpace.AXIS_X; + int yRotationDof = 3 + PhysicsSpace.AXIS_Y; + int zRotationDof = 3 + PhysicsSpace.AXIS_Z; + + // Lock the X and Z rotation DOFs. + joint.set(MotorParam.LowerLimit, xRotationDof, 0f); + joint.set(MotorParam.LowerLimit, zRotationDof, 0f); + joint.set(MotorParam.UpperLimit, xRotationDof, 0f); + joint.set(MotorParam.UpperLimit, zRotationDof, 0f); + + // Limit the Y rotation DOF. + joint.set(MotorParam.LowerLimit, yRotationDof, 0f); + joint.set(MotorParam.UpperLimit, yRotationDof, 1.2f); + + // Enable the motor for Y rotation. + final RotationMotor motor = joint.getRotationMotor(PhysicsSpace.AXIS_Y); + motor.set(MotorParam.TargetVelocity, 0.4f); + motor.setMotorEnabled(true); + motor.setServoEnabled(true); + + // Configure the InputManager to respond to the 1/2/3/4 keys. + inputManager.addMapping("pos1", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("pos2", new KeyTrigger(KeyInput.KEY_2)); + inputManager.addMapping("pos3", new KeyTrigger(KeyInput.KEY_3)); + inputManager.addMapping("pos4", new KeyTrigger(KeyInput.KEY_4)); + InputListener actionListener = new ActionListener() { + @Override + public void onAction(String action, boolean ongoing, float tpf) { + if (!ongoing) { + return; // ignore key release + } + + // Swing the door to the requested position. + switch (action) { + case "pos1": + motor.set(MotorParam.ServoTarget, 1.2f); + break; + case "pos2": + motor.set(MotorParam.ServoTarget, 0.8f); + break; + case "pos3": + motor.set(MotorParam.ServoTarget, 0.4f); + break; + case "pos4": + motor.set(MotorParam.ServoTarget, 0f); + break; + default: + } + } + }; + inputManager.addListener( + actionListener, "pos1", "pos2", "pos3", "pos4"); + } + // ************************************************************************* + // private methods + + /** + * Create a dynamic rigid body with a box shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addDoor() { + BoxCollisionShape shape = new BoxCollisionShape(0.8f, 0.8f, 0.1f); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(result); + + // Disable sleep (deactivation). + result.setEnableSleep(false); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + + return result; + } + + /** + * Create a dynamic body with a square-frame shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addFrame() { + CapsuleCollisionShape xShape + = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_X); + CapsuleCollisionShape yShape + = new CapsuleCollisionShape(0.1f, 2f, PhysicsSpace.AXIS_Y); + + CompoundCollisionShape frameShape = new CompoundCollisionShape(); + frameShape.addChildShape(xShape, 0f, +1f, 0f); + frameShape.addChildShape(xShape, 0f, -1f, 0f); + frameShape.addChildShape(yShape, +1f, 0f, 0f); + frameShape.addChildShape(yShape, -1f, 0f, 0f); + + PhysicsRigidBody result = new PhysicsRigidBody(frameShape); + physicsSpace.addCollisionObject(result); + + Material greenMaterial = createLitMaterial(0f, 1f, 0f); + result.setDebugMaterial(greenMaterial); + + return result; + } + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Position the camera during startup. + */ + private void configureCamera() { + flyCam.setMoveSpeed(5f); + + cam.setLocation(new Vector3f(0f, 1.5f, 4f)); + cam.setRotation(new Quaternion(0.003f, 0.98271f, -0.1846f, 0.014f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + result.setGravity(Vector3f.ZERO); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftBody.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftBody.java index 1c3ac58fd..7c9654e18 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftBody.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftBody.java @@ -1,154 +1,154 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.control.SoftBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; - -/** - * A simple example of a soft body. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloSoftBody extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSoftSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloSoftBody application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloSoftBody application = new HelloSoftBody(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics. - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - // Add a box to the scene and relocate the camera. - addBox(); - cam.setLocation(new Vector3f(0f, 1f, 8f)); - - // Add a light to the scene. - Vector3f direction = new Vector3f(1f, -2f, -4f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction); - rootNode.addLight(sun); - - // Add a model to the scene. - Spatial cgModel = assetManager.loadModel( - "Models/MonkeyHead/MonkeyHead.mesh.xml"); - rootNode.attachChild(cgModel); - - // Add a soft-body control to the model. - SoftBodyControl sbc = new SoftBodyControl(); - cgModel.addControl(sbc); - /* - * Translate and rotate the model's physics body. Since the control - * is "dynamic", the model will follow its body. - */ - PhysicsSoftBody body = sbc.getBody(); - body.applyRotation(new Quaternion().fromAngles(0.4f, 0f, 1f)); - body.applyTranslation(new Vector3f(0f, 3f, 0f)); - /* - * Set the body's default frame pose: if deformed, - * it will tend to return to its current shape. - */ - boolean setVolumePose = false; - boolean setFramePose = true; - body.setPose(setVolumePose, setFramePose); - - // Enable pose matching to make the body bouncy. - SoftBodyConfig config = body.getSoftConfig(); - config.set(Sbcp.PoseMatching, 0.05f); - - sbc.setPhysicsSpace(physicsSpace); - } - // ************************************************************************* - // private methods - - /** - * Add a large static cube to serve as a platform. - */ - private void addBox() { - float halfExtent = 2f; // mesh units - Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); - Geometry geometry = new Geometry("cube platform", mesh); - rootNode.attachChild(geometry); - - geometry.move(0f, -halfExtent, 0f); - ColorRGBA color = new ColorRGBA(0.1f, 0.4f, 0.1f, 1f); - Material material = new Material(assetManager, Materials.LIGHTING); - material.setBoolean("UseMaterialColors", true); - material.setColor("Diffuse", color); - geometry.setMaterial(material); - geometry.setShadowMode(RenderQueue.ShadowMode.Receive); - - BoxCollisionShape shape = new BoxCollisionShape(halfExtent); - RigidBodyControl boxBody - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - geometry.addControl(boxBody); - boxBody.setPhysicsSpace(physicsSpace); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.control.SoftBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +/** + * A simple example of a soft body. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloSoftBody extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSoftSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloSoftBody application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloSoftBody application = new HelloSoftBody(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics. + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + // Add a box to the scene and relocate the camera. + addBox(); + cam.setLocation(new Vector3f(0f, 1f, 8f)); + + // Add a light to the scene. + Vector3f direction = new Vector3f(1f, -2f, -4f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction); + rootNode.addLight(sun); + + // Add a model to the scene. + Spatial cgModel = assetManager.loadModel( + "Models/MonkeyHead/MonkeyHead.mesh.xml"); + rootNode.attachChild(cgModel); + + // Add a soft-body control to the model. + SoftBodyControl sbc = new SoftBodyControl(); + cgModel.addControl(sbc); + /* + * Translate and rotate the model's physics body. Since the control + * is "dynamic", the model will follow its body. + */ + PhysicsSoftBody body = sbc.getBody(); + body.applyRotation(new Quaternion().fromAngles(0.4f, 0f, 1f)); + body.applyTranslation(new Vector3f(0f, 3f, 0f)); + /* + * Set the body's default frame pose: if deformed, + * it will tend to return to its current shape. + */ + boolean setVolumePose = false; + boolean setFramePose = true; + body.setPose(setVolumePose, setFramePose); + + // Enable pose matching to make the body bouncy. + SoftBodyConfig config = body.getSoftConfig(); + config.set(Sbcp.PoseMatching, 0.05f); + + sbc.setPhysicsSpace(physicsSpace); + } + // ************************************************************************* + // private methods + + /** + * Add a large static cube to serve as a platform. + */ + private void addBox() { + float halfExtent = 2f; // mesh units + Mesh mesh = new Box(halfExtent, halfExtent, halfExtent); + Geometry geometry = new Geometry("cube platform", mesh); + rootNode.attachChild(geometry); + + geometry.move(0f, -halfExtent, 0f); + ColorRGBA color = new ColorRGBA(0.1f, 0.4f, 0.1f, 1f); + Material material = new Material(assetManager, Materials.LIGHTING); + material.setBoolean("UseMaterialColors", true); + material.setColor("Diffuse", color); + geometry.setMaterial(material); + geometry.setShadowMode(RenderQueue.ShadowMode.Receive); + + BoxCollisionShape shape = new BoxCollisionShape(halfExtent); + RigidBodyControl boxBody + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + geometry.addControl(boxBody); + boxBody.setPhysicsSpace(physicsSpace); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftRope.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftRope.java index afe4e4549..e1963198b 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftRope.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftRope.java @@ -1,91 +1,91 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import jme3utilities.mesh.DividedLine; - -/** - * A simple rope simulation using a soft body. - *

- * Builds upon HelloPin. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloSoftRope extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloSoftRope application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloSoftRope application = new HelloSoftRope(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - // Relocate the camera. - cam.setLocation(new Vector3f(0f, 1f, 8f)); - - // Generate a subdivided line segment. - int numSegments = 40; - Vector3f endPoint1 = new Vector3f(0f, 4f, 0f); - Vector3f endPoint2 = new Vector3f(2f, 4f, 2f); - Mesh lineMesh = new DividedLine(endPoint1, endPoint2, numSegments); - - // Create a soft body and add it to the physics space. - PhysicsSoftBody rope = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromLineMesh(lineMesh, rope); - physicsSpace.addCollisionObject(rope); - - // Pin one of the end nodes by setting its mass to zero. - int nodeIndex = 0; - rope.setNodeMass(nodeIndex, PhysicsBody.massForStatic); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import jme3utilities.mesh.DividedLine; + +/** + * A simple rope simulation using a soft body. + *

+ * Builds upon HelloPin. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloSoftRope extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloSoftRope application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloSoftRope application = new HelloSoftRope(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + // Relocate the camera. + cam.setLocation(new Vector3f(0f, 1f, 8f)); + + // Generate a subdivided line segment. + int numSegments = 40; + Vector3f endPoint1 = new Vector3f(0f, 4f, 0f); + Vector3f endPoint2 = new Vector3f(2f, 4f, 2f); + Mesh lineMesh = new DividedLine(endPoint1, endPoint2, numSegments); + + // Create a soft body and add it to the physics space. + PhysicsSoftBody rope = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromLineMesh(lineMesh, rope); + physicsSpace.addCollisionObject(rope); + + // Pin one of the end nodes by setting its mass to zero. + int nodeIndex = 0; + rope.setNodeMass(nodeIndex, PhysicsBody.massForStatic); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftSoft.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftSoft.java index a98697c08..4c6058e53 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftSoft.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSoftSoft.java @@ -1,121 +1,121 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.ConfigFlag; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import jme3utilities.mesh.Icosphere; - -/** - * A simple example of a soft-soft collision. - *

- * Builds upon HelloSoftBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloSoftSoft extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloSoftSoft application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloSoftSoft application = new HelloSoftSoft(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics (with debug enabled). - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - // Set gravity to zero. - physicsSpace.setGravity(Vector3f.ZERO); // default = default=(0,-9.81,0) - - // Relocate the camera. - cam.setLocation(new Vector3f(0f, 1f, 8f)); - /* - * A mesh is used to generate the shape and topology - * of each soft body. - */ - int numRefinementIterations = 3; - float radius = 1f; - Mesh sphere = new Icosphere(numRefinementIterations, radius); - - // Create 2 squishy balls and add them to the physics space. - PhysicsSoftBody ball1 = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(sphere, ball1); - physicsSpace.addCollisionObject(ball1); - - PhysicsSoftBody ball2 = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(sphere, ball2); - physicsSpace.addCollisionObject(ball2); - /* - * Set each ball's default frame pose: if deformed, - * it will tend to return to its current shape. - */ - boolean setVolumePose = false; - boolean setFramePose = true; - ball1.setPose(setVolumePose, setFramePose); - ball2.setPose(setVolumePose, setFramePose); - - // Enable pose matching to make the balls bouncy. - SoftBodyConfig config1 = ball1.getSoftConfig(); - config1.set(Sbcp.PoseMatching, 0.01f); // default = 0 - SoftBodyConfig config2 = ball2.getSoftConfig(); - config2.set(Sbcp.PoseMatching, 0.01f); - /* - * Enable soft-soft collisions for each ball. - * Clearing all other collision flags disables soft-rigid collisions. - */ - config1.setCollisionFlags(ConfigFlag.VF_SS); // default = SDF_RS - config2.setCollisionFlags(ConfigFlag.VF_SS); - - // Translate ball2 upward and put it on a collision course with ball1. - ball2.applyTranslation(new Vector3f(0f, 3f, 0f)); - ball2.setVelocity(new Vector3f(0f, -1f, 0f)); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.ConfigFlag; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import jme3utilities.mesh.Icosphere; + +/** + * A simple example of a soft-soft collision. + *

+ * Builds upon HelloSoftBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloSoftSoft extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloSoftSoft application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloSoftSoft application = new HelloSoftSoft(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics (with debug enabled). + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + // Set gravity to zero. + physicsSpace.setGravity(Vector3f.ZERO); // default = default=(0,-9.81,0) + + // Relocate the camera. + cam.setLocation(new Vector3f(0f, 1f, 8f)); + /* + * A mesh is used to generate the shape and topology + * of each soft body. + */ + int numRefinementIterations = 3; + float radius = 1f; + Mesh sphere = new Icosphere(numRefinementIterations, radius); + + // Create 2 squishy balls and add them to the physics space. + PhysicsSoftBody ball1 = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(sphere, ball1); + physicsSpace.addCollisionObject(ball1); + + PhysicsSoftBody ball2 = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(sphere, ball2); + physicsSpace.addCollisionObject(ball2); + /* + * Set each ball's default frame pose: if deformed, + * it will tend to return to its current shape. + */ + boolean setVolumePose = false; + boolean setFramePose = true; + ball1.setPose(setVolumePose, setFramePose); + ball2.setPose(setVolumePose, setFramePose); + + // Enable pose matching to make the balls bouncy. + SoftBodyConfig config1 = ball1.getSoftConfig(); + config1.set(Sbcp.PoseMatching, 0.01f); // default = 0 + SoftBodyConfig config2 = ball2.getSoftConfig(); + config2.set(Sbcp.PoseMatching, 0.01f); + /* + * Enable soft-soft collisions for each ball. + * Clearing all other collision flags disables soft-rigid collisions. + */ + config1.setCollisionFlags(ConfigFlag.VF_SS); // default = SDF_RS + config2.setCollisionFlags(ConfigFlag.VF_SS); + + // Translate ball2 upward and put it on a collision course with ball1. + ball2.applyTranslation(new Vector3f(0f, 3f, 0f)); + ball2.setVelocity(new Vector3f(0f, -1f, 0f)); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSpring.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSpring.java index 06b02a360..9ca02185e 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSpring.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloSpring.java @@ -1,391 +1,391 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.RotationOrder; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.joints.New6Dof; -import com.jme3.bullet.joints.motors.MotorParam; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Plane; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.texture.Texture; -import jme3utilities.MeshNormals; - -/** - * A simple example of a PhysicsJoint with springs. - *

- * Builds upon HelloJoint. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloSpring - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * physics-space Y coordinate of the ground plane - */ - final private static float groundY = -2f; - /** - * half the height of the paddle (in physics-space units) - */ - final private static float paddleHalfHeight = 1f; - // ************************************************************************* - // fields - - /** - * mouse-controlled kinematic paddle - */ - private static PhysicsRigidBody paddleBody; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - /** - * latest ground location indicated by the mouse cursor - */ - final private static Vector3f mouseLocation = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloSpring application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloSpring application = new HelloSpring(); - - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - - // Enable gamma correction for accurate lighting. - settings.setGammaCorrection(true); - - // Disable VSync for more frequent mouse-position updates. - settings.setVSync(false); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - physicsSpace = configurePhysics(); - - // Add a static plane to represent the ground. - addPlane(groundY); - - // Add a mouse-controlled kinematic paddle. - addPaddle(); - - // Add a dynamic ball. - PhysicsRigidBody ballBody = addBall(); - - // Add a single-ended physics joint to constrain the ball's center. - Vector3f pivotInBall = new Vector3f(0f, 0f, 0f); - Vector3f pivotInWorld = new Vector3f(0f, 0f, 0f); - Matrix3f rotInBall = Matrix3f.IDENTITY; - Matrix3f rotInPaddle = Matrix3f.IDENTITY; - New6Dof joint = new New6Dof(ballBody, pivotInBall, pivotInWorld, - rotInBall, rotInPaddle, RotationOrder.XYZ); - physicsSpace.addJoint(joint); - - // Free the X and Z translation DOFs. - joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_X, +1f); - joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Z, +1f); - joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_X, -1f); - joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Z, -1f); - - // Configure springs on the X and Z translation DOFs. - joint.enableSpring(PhysicsSpace.AXIS_X, true); - joint.enableSpring(PhysicsSpace.AXIS_Z, true); - joint.set(MotorParam.Stiffness, PhysicsSpace.AXIS_X, 25f); - joint.set(MotorParam.Stiffness, PhysicsSpace.AXIS_Z, 25f); - - // Lock the Y translation at paddle height. - float paddleY = groundY + paddleHalfHeight; - joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Y, paddleY); - joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Y, paddleY); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Calculate the ground location (if any) indicated by the mouse cursor. - Vector2f screenXy = inputManager.getCursorPosition(); - float nearZ = 0f; - Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); - float farZ = 1f; - Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); - if (nearLocation.y > groundY && farLocation.y < groundY) { - float dy = nearLocation.y - farLocation.y; - float t = (nearLocation.y - groundY) / dy; - FastMath.interpolateLinear( - t, nearLocation, farLocation, mouseLocation); - } - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Reposition the paddle based on the mouse location. - Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); - paddleBody.setPhysicsLocation(bodyLocation); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Create a dynamic rigid body with a sphere shape and add it to the space. - * - * @return the new body - */ - private PhysicsRigidBody addBall() { - float radius = 0.4f; - SphereCollisionShape shape = new SphereCollisionShape(radius); - - float mass = 0.2f; - PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); - physicsSpace.addCollisionObject(result); - - // Disable sleep (deactivation). - result.setEnableSleep(false); - - Material yellowMaterial = createLitMaterial(1f, 1f, 0f); - result.setDebugMaterial(yellowMaterial); - result.setDebugMeshNormals(MeshNormals.Facet); - // faceted so that rotations will be visible - - return result; - } - - /** - * Add lighting and shadows to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.6f); - viewPort.addProcessor(dlsr); - } - - /** - * Create a kinematic body with a box shape and add it to the space. - */ - private void addPaddle() { - BoxCollisionShape shape - = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); - paddleBody = new PhysicsRigidBody(shape); - paddleBody.setKinematic(true); - - physicsSpace.addCollisionObject(paddleBody); - - Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); - paddleBody.setDebugMaterial(redMaterial); - paddleBody.setDebugMeshNormals(MeshNormals.Facet); - } - - /** - * Add a horizontal plane body to the space. - * - * @param y (the desired elevation, in physics-space coordinates) - */ - private void addPlane(float y) { - Plane plane = new Plane(Vector3f.UNIT_Y, y); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - // Load a repeating tile texture. - String assetPath = "Textures/greenTile.png"; - boolean flipY = false; - TextureKey key = new TextureKey(assetPath, flipY); - boolean generateMips = true; - key.setGenerateMips(generateMips); - Texture texture = assetManager.loadTexture(key); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - - // Enable anisotropic filtering, to reduce blurring. - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - texture.setAnisotropicFilter(degree); - - // Apply a tiled, unshaded debug material to the body. - Material material = new Material(assetManager, Materials.UNSHADED); - material.setTexture("ColorMap", texture); - body.setDebugMaterial(material); - - // Generate texture coordinates during debug-mesh initialization. - float tileSize = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); - body.setDebugMeshInitListener(planeDmiListener); - - physicsSpace.addCollisionObject(body); - } - - /** - * Disable FlyByCamera during startup. - */ - private void configureCamera() { - flyCam.setEnabled(false); - - cam.setLocation(new Vector3f(0f, 5f, 10f)); - cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - // Reduce the time step for better accuracy. - result.setAccuracy(0.005f); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.RotationOrder; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.joints.New6Dof; +import com.jme3.bullet.joints.motors.MotorParam; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; +import jme3utilities.MeshNormals; + +/** + * A simple example of a PhysicsJoint with springs. + *

+ * Builds upon HelloJoint. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloSpring + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * physics-space Y coordinate of the ground plane + */ + final private static float groundY = -2f; + /** + * half the height of the paddle (in physics-space units) + */ + final private static float paddleHalfHeight = 1f; + // ************************************************************************* + // fields + + /** + * mouse-controlled kinematic paddle + */ + private static PhysicsRigidBody paddleBody; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + /** + * latest ground location indicated by the mouse cursor + */ + final private static Vector3f mouseLocation = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloSpring application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloSpring application = new HelloSpring(); + + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + + // Enable gamma correction for accurate lighting. + settings.setGammaCorrection(true); + + // Disable VSync for more frequent mouse-position updates. + settings.setVSync(false); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + physicsSpace = configurePhysics(); + + // Add a static plane to represent the ground. + addPlane(groundY); + + // Add a mouse-controlled kinematic paddle. + addPaddle(); + + // Add a dynamic ball. + PhysicsRigidBody ballBody = addBall(); + + // Add a single-ended physics joint to constrain the ball's center. + Vector3f pivotInBall = new Vector3f(0f, 0f, 0f); + Vector3f pivotInWorld = new Vector3f(0f, 0f, 0f); + Matrix3f rotInBall = Matrix3f.IDENTITY; + Matrix3f rotInPaddle = Matrix3f.IDENTITY; + New6Dof joint = new New6Dof(ballBody, pivotInBall, pivotInWorld, + rotInBall, rotInPaddle, RotationOrder.XYZ); + physicsSpace.addJoint(joint); + + // Free the X and Z translation DOFs. + joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_X, +1f); + joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Z, +1f); + joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_X, -1f); + joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Z, -1f); + + // Configure springs on the X and Z translation DOFs. + joint.enableSpring(PhysicsSpace.AXIS_X, true); + joint.enableSpring(PhysicsSpace.AXIS_Z, true); + joint.set(MotorParam.Stiffness, PhysicsSpace.AXIS_X, 25f); + joint.set(MotorParam.Stiffness, PhysicsSpace.AXIS_Z, 25f); + + // Lock the Y translation at paddle height. + float paddleY = groundY + paddleHalfHeight; + joint.set(MotorParam.LowerLimit, PhysicsSpace.AXIS_Y, paddleY); + joint.set(MotorParam.UpperLimit, PhysicsSpace.AXIS_Y, paddleY); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Calculate the ground location (if any) indicated by the mouse cursor. + Vector2f screenXy = inputManager.getCursorPosition(); + float nearZ = 0f; + Vector3f nearLocation = cam.getWorldCoordinates(screenXy, nearZ); + float farZ = 1f; + Vector3f farLocation = cam.getWorldCoordinates(screenXy, farZ); + if (nearLocation.y > groundY && farLocation.y < groundY) { + float dy = nearLocation.y - farLocation.y; + float t = (nearLocation.y - groundY) / dy; + FastMath.interpolateLinear( + t, nearLocation, farLocation, mouseLocation); + } + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Reposition the paddle based on the mouse location. + Vector3f bodyLocation = mouseLocation.add(0f, paddleHalfHeight, 0f); + paddleBody.setPhysicsLocation(bodyLocation); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Create a dynamic rigid body with a sphere shape and add it to the space. + * + * @return the new body + */ + private PhysicsRigidBody addBall() { + float radius = 0.4f; + SphereCollisionShape shape = new SphereCollisionShape(radius); + + float mass = 0.2f; + PhysicsRigidBody result = new PhysicsRigidBody(shape, mass); + physicsSpace.addCollisionObject(result); + + // Disable sleep (deactivation). + result.setEnableSleep(false); + + Material yellowMaterial = createLitMaterial(1f, 1f, 0f); + result.setDebugMaterial(yellowMaterial); + result.setDebugMeshNormals(MeshNormals.Facet); + // faceted so that rotations will be visible + + return result; + } + + /** + * Add lighting and shadows to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.6f); + viewPort.addProcessor(dlsr); + } + + /** + * Create a kinematic body with a box shape and add it to the space. + */ + private void addPaddle() { + BoxCollisionShape shape + = new BoxCollisionShape(0.3f, paddleHalfHeight, 1f); + paddleBody = new PhysicsRigidBody(shape); + paddleBody.setKinematic(true); + + physicsSpace.addCollisionObject(paddleBody); + + Material redMaterial = createLitMaterial(1f, 0.1f, 0.1f); + paddleBody.setDebugMaterial(redMaterial); + paddleBody.setDebugMeshNormals(MeshNormals.Facet); + } + + /** + * Add a horizontal plane body to the space. + * + * @param y (the desired elevation, in physics-space coordinates) + */ + private void addPlane(float y) { + Plane plane = new Plane(Vector3f.UNIT_Y, y); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + // Load a repeating tile texture. + String assetPath = "Textures/greenTile.png"; + boolean flipY = false; + TextureKey key = new TextureKey(assetPath, flipY); + boolean generateMips = true; + key.setGenerateMips(generateMips); + Texture texture = assetManager.loadTexture(key); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + + // Enable anisotropic filtering, to reduce blurring. + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + texture.setAnisotropicFilter(degree); + + // Apply a tiled, unshaded debug material to the body. + Material material = new Material(assetManager, Materials.UNSHADED); + material.setTexture("ColorMap", texture); + body.setDebugMaterial(material); + + // Generate texture coordinates during debug-mesh initialization. + float tileSize = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); + body.setDebugMeshInitListener(planeDmiListener); + + physicsSpace.addCollisionObject(body); + } + + /** + * Disable FlyByCamera during startup. + */ + private void configureCamera() { + flyCam.setEnabled(false); + + cam.setLocation(new Vector3f(0f, 5f, 10f)); + cam.setRotation(new Quaternion(0f, 0.95f, -0.3122f, 0f)); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + // Reduce the time step for better accuracy. + result.setAccuracy(0.005f); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloStaticBody.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloStaticBody.java index ddbfa6639..c50a149cd 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloStaticBody.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloStaticBody.java @@ -1,94 +1,94 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * A simple example combining static and dynamic rigid bodies. - *

- * Builds upon HelloRigidBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloStaticBody extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloStaticBody application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloStaticBody application = new HelloStaticBody(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Set up Bullet physics and create a physics space. - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Create a CollisionShape for balls. - float ballRadius = 1f; - CollisionShape ballShape = new SphereCollisionShape(ballRadius); - - // Create a dynamic body and add it to the space. - float mass = 2f; - PhysicsRigidBody dynaBall = new PhysicsRigidBody(ballShape, mass); - physicsSpace.addCollisionObject(dynaBall); - - // Create a static body and add it to the space. - PhysicsRigidBody statBall - = new PhysicsRigidBody(ballShape, PhysicsBody.massForStatic); - physicsSpace.addCollisionObject(statBall); - - // Position the balls in physics space. - dynaBall.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - statBall.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); - - // Minie's BulletAppState simulates the dynamics... - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.math.Vector3f; + +/** + * A simple example combining static and dynamic rigid bodies. + *

+ * Builds upon HelloRigidBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloStaticBody extends SimpleApplication { + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloStaticBody application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloStaticBody application = new HelloStaticBody(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Set up Bullet physics and create a physics space. + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Create a CollisionShape for balls. + float ballRadius = 1f; + CollisionShape ballShape = new SphereCollisionShape(ballRadius); + + // Create a dynamic body and add it to the space. + float mass = 2f; + PhysicsRigidBody dynaBall = new PhysicsRigidBody(ballShape, mass); + physicsSpace.addCollisionObject(dynaBall); + + // Create a static body and add it to the space. + PhysicsRigidBody statBall + = new PhysicsRigidBody(ballShape, PhysicsBody.massForStatic); + physicsSpace.addCollisionObject(statBall); + + // Position the balls in physics space. + dynaBall.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + statBall.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); + + // Minie's BulletAppState simulates the dynamics... + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloUpdate.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloUpdate.java index c1378015a..d454c342e 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloUpdate.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloUpdate.java @@ -1,156 +1,156 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Sphere; -import com.jme3.system.AppSettings; - -/** - * A simple example without an AppState. - *

- * Builds upon HelloRbc. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloUpdate extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloUpdate application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloUpdate application = new HelloUpdate(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - // Create the physics space. - physicsSpace = new PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT); - - // Create a material and a mesh for balls. - float ballRadius = 1f; - Material ballMaterial = new Material(assetManager, Materials.LIGHTING); - Mesh ballMesh = new Sphere(16, 32, ballRadius); - - // Create geometries for a dynamic ball and a static ball - // and add them to the scene graph. - Geometry dyna = new Geometry("dyna", ballMesh); - dyna.setMaterial(ballMaterial); - rootNode.attachChild(dyna); - - Geometry stat = new Geometry("stat", ballMesh); - stat.setMaterial(ballMaterial); - rootNode.attachChild(stat); - - // Create RBCs for both balls and add them to the geometries. - float mass = 2f; - RigidBodyControl dynaRbc = new RigidBodyControl(mass); - dyna.addControl(dynaRbc); - - RigidBodyControl statRbc - = new RigidBodyControl(PhysicsBody.massForStatic); - stat.addControl(statRbc); - - // Add the controls to the physics space. - dynaRbc.setPhysicsSpace(physicsSpace); - statRbc.setPhysicsSpace(physicsSpace); - - // Position the balls in physics space. - dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); - statRbc.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); - - // Add lighting. - addLighting(rootNode); - } - - /** - * Callback invoked once per frame to update the PhysicsSpace. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - physicsSpace.update(tpf); - } - // ************************************************************************* - // private methods - - /** - * Add lighting to the specified scene. - * - * @param scene the scene to augment (not null) - */ - private static void addLighting(Spatial scene) { - // Light the scene with ambient and directional lights. - ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + +/** + * A simple example without an AppState. + *

+ * Builds upon HelloRbc. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloUpdate extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloUpdate application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloUpdate application = new HelloUpdate(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + // Create the physics space. + physicsSpace = new PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT); + + // Create a material and a mesh for balls. + float ballRadius = 1f; + Material ballMaterial = new Material(assetManager, Materials.LIGHTING); + Mesh ballMesh = new Sphere(16, 32, ballRadius); + + // Create geometries for a dynamic ball and a static ball + // and add them to the scene graph. + Geometry dyna = new Geometry("dyna", ballMesh); + dyna.setMaterial(ballMaterial); + rootNode.attachChild(dyna); + + Geometry stat = new Geometry("stat", ballMesh); + stat.setMaterial(ballMaterial); + rootNode.attachChild(stat); + + // Create RBCs for both balls and add them to the geometries. + float mass = 2f; + RigidBodyControl dynaRbc = new RigidBodyControl(mass); + dyna.addControl(dynaRbc); + + RigidBodyControl statRbc + = new RigidBodyControl(PhysicsBody.massForStatic); + stat.addControl(statRbc); + + // Add the controls to the physics space. + dynaRbc.setPhysicsSpace(physicsSpace); + statRbc.setPhysicsSpace(physicsSpace); + + // Position the balls in physics space. + dynaRbc.setPhysicsLocation(new Vector3f(0f, 4f, 0f)); + statRbc.setPhysicsLocation(new Vector3f(0.1f, 0f, 0f)); + + // Add lighting. + addLighting(rootNode); + } + + /** + * Callback invoked once per frame to update the PhysicsSpace. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + physicsSpace.update(tpf); + } + // ************************************************************************* + // private methods + + /** + * Add lighting to the specified scene. + * + * @param scene the scene to augment (not null) + */ + private static void addLighting(Spatial scene) { + // Light the scene with ambient and directional lights. + ColorRGBA ambientColor = new ColorRGBA(0.02f, 0.02f, 0.02f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.2f, 0.2f, 0.2f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloVehicle.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloVehicle.java index 1e0186cd6..dab5cf77c 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloVehicle.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloVehicle.java @@ -1,194 +1,194 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.TextureKey; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.PlaneCollisionShape; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsVehicle; -import com.jme3.bullet.util.PlaneDmiListener; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.FastMath; -import com.jme3.math.Plane; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Limits; -import com.jme3.texture.Texture; -import java.util.ArrayList; -import java.util.Collection; - -/** - * A simple example of vehicle physics. - *

- * Builds upon HelloStaticBody. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloVehicle extends SimpleApplication { - // ************************************************************************* - // fields - - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloVehicle application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloVehicle application = new HelloVehicle(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - physicsSpace = configurePhysics(); - - // Create a wedge-shaped vehicle with a low center of gravity. - // The local forward direction is +Z. - float noseZ = 1.4f; // offset from chassis center - float spoilerY = 0.5f; // offset from chassis center - float tailZ = -0.7f; // offset from chassis center - float undercarriageY = -0.1f; // offset from chassis center - float halfWidth = 0.4f; - Collection cornerLocations = new ArrayList<>(6); - cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, noseZ)); - cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, noseZ)); - cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, tailZ)); - cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, tailZ)); - cornerLocations.add(new Vector3f(+halfWidth, spoilerY, tailZ)); - cornerLocations.add(new Vector3f(-halfWidth, spoilerY, tailZ)); - HullCollisionShape wedgeShape - = new HullCollisionShape(cornerLocations); - float mass = 5f; - PhysicsVehicle vehicle = new PhysicsVehicle(wedgeShape, mass); - vehicle.setSuspensionCompression(6f); // default=0.83 - vehicle.setSuspensionDamping(7f); // default=0.88 - vehicle.setSuspensionStiffness(150f); // default=5.88 - - // Add 4 wheels, 2 in the front (for steering) and 2 in the rear. - boolean front = true; - boolean rear = false; - float frontAxisZ = 0.7f * noseZ; // offset from chassis center - float rearAxisZ = 0.8f * tailZ; // offset from chassis center - float radius = 0.3f; // of each tire - float restLength = 0.2f; // of the suspension - float xOffset = 0.9f * halfWidth; - Vector3f axleDirection = new Vector3f(-1f, 0f, 0f); - Vector3f suspensionDirection = new Vector3f(0f, -1f, 0f); - vehicle.addWheel(new Vector3f(-xOffset, 0f, frontAxisZ), - suspensionDirection, axleDirection, restLength, radius, front); - vehicle.addWheel(new Vector3f(xOffset, 0f, frontAxisZ), - suspensionDirection, axleDirection, restLength, radius, front); - vehicle.addWheel(new Vector3f(-xOffset, 0f, rearAxisZ), - suspensionDirection, axleDirection, restLength, radius, rear); - vehicle.addWheel(new Vector3f(xOffset, 0f, rearAxisZ), - suspensionDirection, axleDirection, restLength, radius, rear); - - physicsSpace.addCollisionObject(vehicle); - - // Apply a steering angle of 6 degrees left (to the front wheels). - vehicle.steer(FastMath.PI / 30f); - - // Apply a constant acceleration (to the chassis). - vehicle.accelerate(1f); - - // Add a static plane to represent the ground. - float y = -radius - 0.35f; - addPlane(y); - } - // ************************************************************************* - // private methods - - /** - * Add a horizontal plane body to the space. - * - * @param y (the desired elevation, in physics-space coordinates) - */ - private void addPlane(float y) { - Plane plane = new Plane(Vector3f.UNIT_Y, y); - PlaneCollisionShape shape = new PlaneCollisionShape(plane); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - // Load a repeating tile texture. - String assetPath = "Textures/greenTile.png"; - boolean flipY = false; - TextureKey key = new TextureKey(assetPath, flipY); - boolean generateMips = true; - key.setGenerateMips(generateMips); - Texture texture = assetManager.loadTexture(key); - texture.setMinFilter(Texture.MinFilter.Trilinear); - texture.setWrap(Texture.WrapMode.Repeat); - - // Enable anisotropic filtering, to reduce blurring. - Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); - int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); - texture.setAnisotropicFilter(degree); - - // Apply a tiled, unshaded debug material to the body. - Material material = new Material(assetManager, Materials.UNSHADED); - material.setTexture("ColorMap", texture); - body.setDebugMaterial(material); - - // Generate texture coordinates during debug-mesh initialization. - float tileSize = 1f; - PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); - body.setDebugMeshInitListener(planeDmiListener); - - physicsSpace.addCollisionObject(body); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.PlaneCollisionShape; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.bullet.objects.PhysicsVehicle; +import com.jme3.bullet.util.PlaneDmiListener; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.FastMath; +import com.jme3.math.Plane; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Limits; +import com.jme3.texture.Texture; +import java.util.ArrayList; +import java.util.Collection; + +/** + * A simple example of vehicle physics. + *

+ * Builds upon HelloStaticBody. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloVehicle extends SimpleApplication { + // ************************************************************************* + // fields + + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloVehicle application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloVehicle application = new HelloVehicle(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + physicsSpace = configurePhysics(); + + // Create a wedge-shaped vehicle with a low center of gravity. + // The local forward direction is +Z. + float noseZ = 1.4f; // offset from chassis center + float spoilerY = 0.5f; // offset from chassis center + float tailZ = -0.7f; // offset from chassis center + float undercarriageY = -0.1f; // offset from chassis center + float halfWidth = 0.4f; + Collection cornerLocations = new ArrayList<>(6); + cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, noseZ)); + cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, noseZ)); + cornerLocations.add(new Vector3f(+halfWidth, undercarriageY, tailZ)); + cornerLocations.add(new Vector3f(-halfWidth, undercarriageY, tailZ)); + cornerLocations.add(new Vector3f(+halfWidth, spoilerY, tailZ)); + cornerLocations.add(new Vector3f(-halfWidth, spoilerY, tailZ)); + HullCollisionShape wedgeShape + = new HullCollisionShape(cornerLocations); + float mass = 5f; + PhysicsVehicle vehicle = new PhysicsVehicle(wedgeShape, mass); + vehicle.setSuspensionCompression(6f); // default=0.83 + vehicle.setSuspensionDamping(7f); // default=0.88 + vehicle.setSuspensionStiffness(150f); // default=5.88 + + // Add 4 wheels, 2 in the front (for steering) and 2 in the rear. + boolean front = true; + boolean rear = false; + float frontAxisZ = 0.7f * noseZ; // offset from chassis center + float rearAxisZ = 0.8f * tailZ; // offset from chassis center + float radius = 0.3f; // of each tire + float restLength = 0.2f; // of the suspension + float xOffset = 0.9f * halfWidth; + Vector3f axleDirection = new Vector3f(-1f, 0f, 0f); + Vector3f suspensionDirection = new Vector3f(0f, -1f, 0f); + vehicle.addWheel(new Vector3f(-xOffset, 0f, frontAxisZ), + suspensionDirection, axleDirection, restLength, radius, front); + vehicle.addWheel(new Vector3f(xOffset, 0f, frontAxisZ), + suspensionDirection, axleDirection, restLength, radius, front); + vehicle.addWheel(new Vector3f(-xOffset, 0f, rearAxisZ), + suspensionDirection, axleDirection, restLength, radius, rear); + vehicle.addWheel(new Vector3f(xOffset, 0f, rearAxisZ), + suspensionDirection, axleDirection, restLength, radius, rear); + + physicsSpace.addCollisionObject(vehicle); + + // Apply a steering angle of 6 degrees left (to the front wheels). + vehicle.steer(FastMath.PI / 30f); + + // Apply a constant acceleration (to the chassis). + vehicle.accelerate(1f); + + // Add a static plane to represent the ground. + float y = -radius - 0.35f; + addPlane(y); + } + // ************************************************************************* + // private methods + + /** + * Add a horizontal plane body to the space. + * + * @param y (the desired elevation, in physics-space coordinates) + */ + private void addPlane(float y) { + Plane plane = new Plane(Vector3f.UNIT_Y, y); + PlaneCollisionShape shape = new PlaneCollisionShape(plane); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + // Load a repeating tile texture. + String assetPath = "Textures/greenTile.png"; + boolean flipY = false; + TextureKey key = new TextureKey(assetPath, flipY); + boolean generateMips = true; + key.setGenerateMips(generateMips); + Texture texture = assetManager.loadTexture(key); + texture.setMinFilter(Texture.MinFilter.Trilinear); + texture.setWrap(Texture.WrapMode.Repeat); + + // Enable anisotropic filtering, to reduce blurring. + Integer maxDegree = renderer.getLimits().get(Limits.TextureAnisotropy); + int degree = (maxDegree == null) ? 1 : Math.min(8, maxDegree); + texture.setAnisotropicFilter(degree); + + // Apply a tiled, unshaded debug material to the body. + Material material = new Material(assetManager, Materials.UNSHADED); + material.setTexture("ColorMap", texture); + body.setDebugMaterial(material); + + // Generate texture coordinates during debug-mesh initialization. + float tileSize = 1f; + PlaneDmiListener planeDmiListener = new PlaneDmiListener(tileSize); + body.setDebugMeshInitListener(planeDmiListener); + + physicsSpace.addCollisionObject(body); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalk.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalk.java index bbf298bd0..a62e5b4a0 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalk.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalk.java @@ -1,349 +1,349 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.debug.DebugInitListener; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsCharacter; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.InputListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.terrain.heightmap.HeightMap; -import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import jme3utilities.MeshNormals; - -/** - * A simple example of character physics. - *

- * Press the W key to walk. Press the space bar to jump. - *

- * Builds upon HelloCharacter. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloWalk - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // fields - - /** - * true when the spacebar is pressed, otherwise false - */ - private static volatile boolean jumpRequested; - /** - * true when the W key is pressed, otherwise false - */ - private static volatile boolean walkRequested; - /** - * character being tested - */ - private static PhysicsCharacter character; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloWalk application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloWalk application = new HelloWalk(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - configureInput(); - physicsSpace = configurePhysics(); - - // Create a character with a capsule shape and add it to the space. - float capsuleRadius = 3f; - float capsuleHeight = 4f; - CapsuleCollisionShape shape - = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); - float stepHeight = 0.01f; - character = new PhysicsCharacter(shape, stepHeight); - character.setGravity(60f); - physicsSpace.addCollisionObject(character); - - // Teleport the character to its initial location. - character.setPhysicsLocation(new Vector3f(-73.6f, 19.09f, -45.58f)); - - // Add a static heightmap to represent the ground. - addTerrain(); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - /* - * Synchronize the (first-person) camera location - * with the PhysicsCharacter. - * This overrides any translation requested by FlyByCamera. - */ - Vector3f location = character.getPhysicsLocation(null); - cam.setLocation(location); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Clear any motion from the previous simulation step. - character.setWalkDirection(Vector3f.ZERO); - /* - * If the character is touching the ground, - * cause it respond to keyboard input. - */ - if (character.onGround()) { - if (jumpRequested) { - character.jump(); - - } else if (walkRequested) { - // Walk in the camera's forward direction. - Vector3f offset = cam.getDirection(); - float walkSpeed = 7f; - offset.multLocal(walkSpeed * timeStep); - character.setWalkDirection(offset); - } - } - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.4f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Add a heightfield body to the space. - */ - private void addTerrain() { - // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar - String assetPath = "Textures/Terrain/splat/mountains512.png"; - Texture texture = assetManager.loadTexture(assetPath); - Image image = texture.getImage(); - HeightMap heightMap = new ImageBasedHeightMap(image); - heightMap.setHeightScale(0.2f); - - // Construct a static rigid body based on the HeightMap. - CollisionShape shape = new HeightfieldCollisionShape(heightMap); - PhysicsRigidBody body - = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - - physicsSpace.addCollisionObject(body); - - // Customize its debug visualization. - Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); - body.setDebugMaterial(greenMaterial); - body.setDebugMeshNormals(MeshNormals.Smooth); - } - - /** - * Configure the Camera during startup. - */ - private void configureCamera() { - float frustumWidth = cam.getFrustumTop() - cam.getFrustumBottom(); - float frustumHeight = cam.getFrustumRight() - cam.getFrustumLeft(); - float aspectRatio = frustumHeight / frustumWidth; - float far = cam.getFrustumFar(); - float fieldOfViewDegrees = 30f; - - // Bring the near plane closer to reduce clipping. - float near = 0.1f; // default = 1 - cam.setFrustumPerspective(fieldOfViewDegrees, aspectRatio, near, far); - } - - /** - * Configure keyboard input during startup. - */ - private void configureInput() { - inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE)); - InputListener input = new ActionListener() { - @Override - public void onAction(String action, boolean isPressed, float tpf) { - switch (action) { - case "jump": - jumpRequested = isPressed; - return; - - case CameraInput.FLYCAM_FORWARD: - walkRequested = isPressed; - return; - - default: - System.out.println("Unknown action: " + action); - } - } - }; - inputManager.addListener(input, "jump", CameraInput.FLYCAM_FORWARD); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - - // Enable debug visualization to reveal what occurs in physics space. - bulletAppState.setDebugEnabled(true); - - // Add lighting and shadows to the debug scene. - bulletAppState.setDebugInitListener(new DebugInitListener() { - @Override - public void bulletDebugInit(Node physicsDebugRootNode) { - addLighting(physicsDebugRootNode); - } - }); - bulletAppState.setDebugShadowMode( - RenderQueue.ShadowMode.CastAndReceive); - - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - // To enable the callbacks, register the application as a tick listener. - result.addTickListener(this); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.debug.DebugInitListener; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsCharacter; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import jme3utilities.MeshNormals; + +/** + * A simple example of character physics. + *

+ * Press the W key to walk. Press the space bar to jump. + *

+ * Builds upon HelloCharacter. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloWalk + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // fields + + /** + * true when the spacebar is pressed, otherwise false + */ + private static volatile boolean jumpRequested; + /** + * true when the W key is pressed, otherwise false + */ + private static volatile boolean walkRequested; + /** + * character being tested + */ + private static PhysicsCharacter character; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloWalk application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloWalk application = new HelloWalk(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + configureInput(); + physicsSpace = configurePhysics(); + + // Create a character with a capsule shape and add it to the space. + float capsuleRadius = 3f; + float capsuleHeight = 4f; + CapsuleCollisionShape shape + = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); + float stepHeight = 0.01f; + character = new PhysicsCharacter(shape, stepHeight); + character.setGravity(60f); + physicsSpace.addCollisionObject(character); + + // Teleport the character to its initial location. + character.setPhysicsLocation(new Vector3f(-73.6f, 19.09f, -45.58f)); + + // Add a static heightmap to represent the ground. + addTerrain(); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + /* + * Synchronize the (first-person) camera location + * with the PhysicsCharacter. + * This overrides any translation requested by FlyByCamera. + */ + Vector3f location = character.getPhysicsLocation(null); + cam.setLocation(location); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Clear any motion from the previous simulation step. + character.setWalkDirection(Vector3f.ZERO); + /* + * If the character is touching the ground, + * cause it respond to keyboard input. + */ + if (character.onGround()) { + if (jumpRequested) { + character.jump(); + + } else if (walkRequested) { + // Walk in the camera's forward direction. + Vector3f offset = cam.getDirection(); + float walkSpeed = 7f; + offset.multLocal(walkSpeed * timeStep); + character.setWalkDirection(offset); + } + } + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.4f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Add a heightfield body to the space. + */ + private void addTerrain() { + // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar + String assetPath = "Textures/Terrain/splat/mountains512.png"; + Texture texture = assetManager.loadTexture(assetPath); + Image image = texture.getImage(); + HeightMap heightMap = new ImageBasedHeightMap(image); + heightMap.setHeightScale(0.2f); + + // Construct a static rigid body based on the HeightMap. + CollisionShape shape = new HeightfieldCollisionShape(heightMap); + PhysicsRigidBody body + = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + + physicsSpace.addCollisionObject(body); + + // Customize its debug visualization. + Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); + body.setDebugMaterial(greenMaterial); + body.setDebugMeshNormals(MeshNormals.Smooth); + } + + /** + * Configure the Camera during startup. + */ + private void configureCamera() { + float frustumWidth = cam.getFrustumTop() - cam.getFrustumBottom(); + float frustumHeight = cam.getFrustumRight() - cam.getFrustumLeft(); + float aspectRatio = frustumHeight / frustumWidth; + float far = cam.getFrustumFar(); + float fieldOfViewDegrees = 30f; + + // Bring the near plane closer to reduce clipping. + float near = 0.1f; // default = 1 + cam.setFrustumPerspective(fieldOfViewDegrees, aspectRatio, near, far); + } + + /** + * Configure keyboard input during startup. + */ + private void configureInput() { + inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE)); + InputListener input = new ActionListener() { + @Override + public void onAction(String action, boolean isPressed, float tpf) { + switch (action) { + case "jump": + jumpRequested = isPressed; + return; + + case CameraInput.FLYCAM_FORWARD: + walkRequested = isPressed; + return; + + default: + System.out.println("Unknown action: " + action); + } + } + }; + inputManager.addListener(input, "jump", CameraInput.FLYCAM_FORWARD); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + + // Enable debug visualization to reveal what occurs in physics space. + bulletAppState.setDebugEnabled(true); + + // Add lighting and shadows to the debug scene. + bulletAppState.setDebugInitListener(new DebugInitListener() { + @Override + public void bulletDebugInit(Node physicsDebugRootNode) { + addLighting(physicsDebugRootNode); + } + }); + bulletAppState.setDebugShadowMode( + RenderQueue.ShadowMode.CastAndReceive); + + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + // To enable the callbacks, register the application as a tick listener. + result.addTickListener(this); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoBcc.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoBcc.java index 3e0e4be9c..9c1e1a483 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoBcc.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoBcc.java @@ -1,379 +1,379 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.anim.AnimComposer; -import com.jme3.anim.tween.action.Action; -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.control.BetterCharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.terrain.geomipmap.TerrainQuad; -import com.jme3.terrain.heightmap.HeightMap; -import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; - -/** - * An example of character physics using Oto and BetterCharacterControl. - *

- * Press the U/H/J/K keys to walk. Press the space bar to jump. - *

- * Builds upon HelloWalkOtoCc. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloWalkOtoBcc - extends SimpleApplication - implements ActionListener { - // ************************************************************************* - // fields - - private static Action standAction; - private static Action walkAction; - private static AnimComposer composer; - private static BetterCharacterControl character; - /** - * true when the spacebar is pressed, otherwise false - */ - private static volatile boolean jumpRequested; - /** - * true when the U key is pressed, otherwise false - */ - private static volatile boolean walkAway; - /** - * true when the H key is pressed, otherwise false - */ - private static volatile boolean walkLeft; - /** - * true when the K key is pressed, otherwise false - */ - private static volatile boolean walkRight; - /** - * true when the J key is pressed, otherwise false - */ - private static volatile boolean walkToward; - - final private static Node translationNode = new Node("translation node"); - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloWalkOtoBcc application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloWalkOtoBcc application = new HelloWalkOtoBcc(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - addLighting(rootNode); - configureCamera(); - configureInput(); - physicsSpace = configurePhysics(); - - // Load the Oto model and find its animation actions. - Spatial oto = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - composer = oto.getControl(AnimComposer.class); - standAction = composer.action("stand"); - walkAction = composer.action("Walk"); - - // Attach the model to a translation node. - rootNode.attachChild(translationNode); - translationNode.attachChild(oto); - oto.move(0f, 5f, 0f); - - // Create the PhysicsControl and add it to the scene and space. - float characterRadius = 3f; - float characterHeight = 10f; - float characterMass = 70f; - character = new BetterCharacterControl( - characterRadius, characterHeight, characterMass); - character.setJumpForce(new Vector3f(0f, 700f, 0f)); - translationNode.addControl(character); - physicsSpace.add(character); - - // Teleport the character to its initial location. - character.warp(new Vector3f(-73.6f, 14.09f, -45.58f)); - - // Add a static heightmap to represent the ground. - addTerrain(); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Determine horizontal directions relative to the camera orientation. - Vector3f away = cam.getDirection(); - away.y = 0; - away.normalizeLocal(); - - Vector3f left = cam.getLeft(); - left.y = 0; - left.normalizeLocal(); - - // Determine the walk velocity from keyboard inputs. - Vector3f direction = new Vector3f(); - if (walkAway) { - direction.addLocal(away); - } - if (walkLeft) { - direction.addLocal(left); - } - if (walkRight) { - direction.subtractLocal(left); - } - if (walkToward) { - direction.subtractLocal(away); - } - direction.normalizeLocal(); - float walkSpeed = 7f; - Vector3f walkVelocity = direction.mult(walkSpeed); - character.setWalkDirection(walkVelocity); - - // Decide whether to jump. - if (jumpRequested) { - character.jump(); - } - - // Update the animation action. - Action action = composer.getCurrentAction(); - if (walkVelocity.length() < 0.001f) { - if (action != standAction) { - composer.setCurrentAction("stand"); - } - } else { - character.setViewDirection(direction); - if (action != walkAction) { - composer.setCurrentAction("Walk"); - } - } - } - // ************************************************************************* - // ActionListener methods - - /** - * Callback to handle keyboard input events. - * - * @param action the name of the input event - * @param ongoing true → pressed, false → released - * @param tpf the time per frame (in seconds, ≥0) - */ - @Override - public void onAction(String action, boolean ongoing, float tpf) { - switch (action) { - case "jump": - jumpRequested = ongoing; - return; - - case "walk away": - walkAway = ongoing; - return; - - case "walk left": - walkLeft = ongoing; - return; - - case "walk right": - walkRight = ongoing; - return; - - case "walk toward": - walkToward = ongoing; - return; - - default: - System.out.println("Unknown action: " + action); - } - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.4f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Add a heightfield body to the space. - */ - private void addTerrain() { - // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar - String assetPath = "Textures/Terrain/splat/mountains512.png"; - Texture texture = assetManager.loadTexture(assetPath); - Image image = texture.getImage(); - HeightMap heightMap = new ImageBasedHeightMap(image); - heightMap.setHeightScale(0.2f); - - heightMap.load(); - TerrainQuad terrain - = new TerrainQuad("terrain", 65, 513, heightMap.getHeightMap()); - rootNode.attachChild(terrain); - Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); - terrain.setMaterial(greenMaterial); - - // Construct a static RigidBodyControl based on the HeightMap. - CollisionShape shape = new HeightfieldCollisionShape(heightMap); - RigidBodyControl rbc - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - rbc.setPhysicsSpace(physicsSpace); - terrain.addControl(rbc); - } - - /** - * Configure the Camera during startup. - */ - private void configureCamera() { - flyCam.setMoveSpeed(10f); - - cam.setLocation(new Vector3f(-39f, 34f, -47f)); - cam.setRotation(new Quaternion(0.183f, -0.68302f, 0.183f, 0.68302f)); - } - - /** - * Configure keyboard input during startup. - */ - private void configureInput() { - inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addMapping("walk away", new KeyTrigger(KeyInput.KEY_U)); - inputManager.addMapping("walk left", new KeyTrigger(KeyInput.KEY_H)); - inputManager.addMapping("walk right", new KeyTrigger(KeyInput.KEY_K)); - inputManager.addMapping("walk toward", new KeyTrigger(KeyInput.KEY_J)); - inputManager.addListener(this, - "jump", "walk away", "walk left", "walk right", "walk toward"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - result.setGravity(new Vector3f(0f, -60f, 0f)); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.tween.action.Action; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; + +/** + * An example of character physics using Oto and BetterCharacterControl. + *

+ * Press the U/H/J/K keys to walk. Press the space bar to jump. + *

+ * Builds upon HelloWalkOtoCc. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloWalkOtoBcc + extends SimpleApplication + implements ActionListener { + // ************************************************************************* + // fields + + private static Action standAction; + private static Action walkAction; + private static AnimComposer composer; + private static BetterCharacterControl character; + /** + * true when the spacebar is pressed, otherwise false + */ + private static volatile boolean jumpRequested; + /** + * true when the U key is pressed, otherwise false + */ + private static volatile boolean walkAway; + /** + * true when the H key is pressed, otherwise false + */ + private static volatile boolean walkLeft; + /** + * true when the K key is pressed, otherwise false + */ + private static volatile boolean walkRight; + /** + * true when the J key is pressed, otherwise false + */ + private static volatile boolean walkToward; + + final private static Node translationNode = new Node("translation node"); + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloWalkOtoBcc application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloWalkOtoBcc application = new HelloWalkOtoBcc(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + addLighting(rootNode); + configureCamera(); + configureInput(); + physicsSpace = configurePhysics(); + + // Load the Oto model and find its animation actions. + Spatial oto = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + composer = oto.getControl(AnimComposer.class); + standAction = composer.action("stand"); + walkAction = composer.action("Walk"); + + // Attach the model to a translation node. + rootNode.attachChild(translationNode); + translationNode.attachChild(oto); + oto.move(0f, 5f, 0f); + + // Create the PhysicsControl and add it to the scene and space. + float characterRadius = 3f; + float characterHeight = 10f; + float characterMass = 70f; + character = new BetterCharacterControl( + characterRadius, characterHeight, characterMass); + character.setJumpForce(new Vector3f(0f, 700f, 0f)); + translationNode.addControl(character); + physicsSpace.add(character); + + // Teleport the character to its initial location. + character.warp(new Vector3f(-73.6f, 14.09f, -45.58f)); + + // Add a static heightmap to represent the ground. + addTerrain(); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Determine horizontal directions relative to the camera orientation. + Vector3f away = cam.getDirection(); + away.y = 0; + away.normalizeLocal(); + + Vector3f left = cam.getLeft(); + left.y = 0; + left.normalizeLocal(); + + // Determine the walk velocity from keyboard inputs. + Vector3f direction = new Vector3f(); + if (walkAway) { + direction.addLocal(away); + } + if (walkLeft) { + direction.addLocal(left); + } + if (walkRight) { + direction.subtractLocal(left); + } + if (walkToward) { + direction.subtractLocal(away); + } + direction.normalizeLocal(); + float walkSpeed = 7f; + Vector3f walkVelocity = direction.mult(walkSpeed); + character.setWalkDirection(walkVelocity); + + // Decide whether to jump. + if (jumpRequested) { + character.jump(); + } + + // Update the animation action. + Action action = composer.getCurrentAction(); + if (walkVelocity.length() < 0.001f) { + if (action != standAction) { + composer.setCurrentAction("stand"); + } + } else { + character.setViewDirection(direction); + if (action != walkAction) { + composer.setCurrentAction("Walk"); + } + } + } + // ************************************************************************* + // ActionListener methods + + /** + * Callback to handle keyboard input events. + * + * @param action the name of the input event + * @param ongoing true → pressed, false → released + * @param tpf the time per frame (in seconds, ≥0) + */ + @Override + public void onAction(String action, boolean ongoing, float tpf) { + switch (action) { + case "jump": + jumpRequested = ongoing; + return; + + case "walk away": + walkAway = ongoing; + return; + + case "walk left": + walkLeft = ongoing; + return; + + case "walk right": + walkRight = ongoing; + return; + + case "walk toward": + walkToward = ongoing; + return; + + default: + System.out.println("Unknown action: " + action); + } + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.4f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Add a heightfield body to the space. + */ + private void addTerrain() { + // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar + String assetPath = "Textures/Terrain/splat/mountains512.png"; + Texture texture = assetManager.loadTexture(assetPath); + Image image = texture.getImage(); + HeightMap heightMap = new ImageBasedHeightMap(image); + heightMap.setHeightScale(0.2f); + + heightMap.load(); + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightMap.getHeightMap()); + rootNode.attachChild(terrain); + Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); + terrain.setMaterial(greenMaterial); + + // Construct a static RigidBodyControl based on the HeightMap. + CollisionShape shape = new HeightfieldCollisionShape(heightMap); + RigidBodyControl rbc + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + rbc.setPhysicsSpace(physicsSpace); + terrain.addControl(rbc); + } + + /** + * Configure the Camera during startup. + */ + private void configureCamera() { + flyCam.setMoveSpeed(10f); + + cam.setLocation(new Vector3f(-39f, 34f, -47f)); + cam.setRotation(new Quaternion(0.183f, -0.68302f, 0.183f, 0.68302f)); + } + + /** + * Configure keyboard input during startup. + */ + private void configureInput() { + inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("walk away", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("walk left", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("walk right", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("walk toward", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(this, + "jump", "walk away", "walk left", "walk right", "walk toward"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + result.setGravity(new Vector3f(0f, -60f, 0f)); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoCc.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoCc.java index e48950494..d974ca505 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoCc.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWalkOtoCc.java @@ -1,376 +1,376 @@ -/* - Copyright (c) 2020-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.anim.AnimComposer; -import com.jme3.anim.tween.action.Action; -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; -import com.jme3.bullet.control.CharacterControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.AmbientLight; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.material.Materials; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowRenderer; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.system.AppSettings; -import com.jme3.terrain.geomipmap.TerrainQuad; -import com.jme3.terrain.heightmap.HeightMap; -import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; - -/** - * An example of character physics using Oto and CharacterControl. - *

- * Press the U/H/J/K keys to walk. Press the space bar to jump. - *

- * Builds upon HelloWalk. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloWalkOtoCc - extends SimpleApplication - implements ActionListener { - // ************************************************************************* - // fields - - private static Action standAction; - private static Action walkAction; - private static AnimComposer composer; - /** - * true when the spacebar is pressed, otherwise false - */ - private static volatile boolean jumpRequested; - /** - * true when the U key is pressed, otherwise false - */ - private static volatile boolean walkAway; - /** - * true when the H key is pressed, otherwise false - */ - private static volatile boolean walkLeft; - /** - * true when the K key is pressed, otherwise false - */ - private static volatile boolean walkRight; - /** - * true when the J key is pressed, otherwise false - */ - private static volatile boolean walkToward; - - private static CharacterControl character; - /** - * PhysicsSpace for simulation - */ - private static PhysicsSpace physicsSpace; - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloWalkOtoCc application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloWalkOtoCc application = new HelloWalkOtoCc(); - - // Enable gamma correction for accurate lighting. - boolean loadDefaults = true; - AppSettings settings = new AppSettings(loadDefaults); - settings.setGammaCorrection(true); - application.setSettings(settings); - - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - addLighting(rootNode); - configureCamera(); - configureInput(); - physicsSpace = configurePhysics(); - - // Load the Oto model and find its animation actions. - Spatial oto = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - composer = oto.getControl(AnimComposer.class); - standAction = composer.action("stand"); - walkAction = composer.action("Walk"); - - rootNode.attachChild(oto); - - // Create the PhysicsControl and add it to the scene and space. - float capsuleRadius = 3f; - float capsuleHeight = 4f; - CapsuleCollisionShape shape - = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); - float stepHeight = 0.01f; - character = new CharacterControl(shape, stepHeight); - character.setGravity(60f); - oto.addControl(character); - physicsSpace.add(character); - - // Teleport the character to its initial location. - character.setPhysicsLocation(new Vector3f(-73.6f, 19.09f, -45.58f)); - - // Add a static heightmap to represent the ground. - addTerrain(); - } - - /** - * Callback invoked once per frame. - * - * @param tpf the time interval between frames (in seconds, ≥0) - */ - @Override - public void simpleUpdate(float tpf) { - // Determine horizontal directions relative to the camera orientation. - Vector3f away = cam.getDirection(); - away.y = 0; - away.normalizeLocal(); - - Vector3f left = cam.getLeft(); - left.y = 0; - left.normalizeLocal(); - - // Determine the walk velocity from keyboard inputs. - Vector3f direction = new Vector3f(); - if (walkAway) { - direction.addLocal(away); - } - if (walkLeft) { - direction.addLocal(left); - } - if (walkRight) { - direction.subtractLocal(left); - } - if (walkToward) { - direction.subtractLocal(away); - } - direction.normalizeLocal(); - float walkSpeed = 7f; - float timeStep = 1 / 60f; - Vector3f walkOffset = direction.mult(walkSpeed * timeStep); - character.setWalkDirection(walkOffset); - - // Decide whether to jump. - if (jumpRequested) { - character.jump(); - } - - // Update the animation action. - Action action = composer.getCurrentAction(); - if (walkOffset.length() < 0.0001f) { - if (action != standAction) { - composer.setCurrentAction("stand"); - } - } else { - character.setViewDirection(direction); - if (action != walkAction) { - composer.setCurrentAction("Walk"); - } - } - } - // ************************************************************************* - // ActionListener methods - - /** - * Callback to handle keyboard input events. - * - * @param action the name of the input event - * @param ongoing true → pressed, false → released - * @param tpf the time per frame (in seconds, ≥0) - */ - @Override - public void onAction(String action, boolean ongoing, float tpf) { - switch (action) { - case "jump": - jumpRequested = ongoing; - return; - - case "walk away": - walkAway = ongoing; - return; - - case "walk left": - walkLeft = ongoing; - return; - - case "walk right": - walkRight = ongoing; - return; - - case "walk toward": - walkToward = ongoing; - return; - - default: - System.out.println("Unknown action: " + action); - } - } - // ************************************************************************* - // private methods - - /** - * Add lighting and shadows to the specified scene and set the background - * color. - * - * @param scene the scene to augment (not null) - */ - private void addLighting(Spatial scene) { - scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); - - ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); - AmbientLight ambient = new AmbientLight(ambientColor); - scene.addLight(ambient); - ambient.setName("ambient"); - - ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); - Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); - DirectionalLight sun = new DirectionalLight(direction, directColor); - scene.addLight(sun); - sun.setName("sun"); - - // Render shadows based on the directional light. - viewPort.clearProcessors(); - int shadowMapSize = 2_048; // in pixels - int numSplits = 3; - DirectionalLightShadowRenderer dlsr - = new DirectionalLightShadowRenderer( - assetManager, shadowMapSize, numSplits); - dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); - dlsr.setEdgesThickness(5); - dlsr.setLight(sun); - dlsr.setShadowIntensity(0.4f); - viewPort.addProcessor(dlsr); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - } - - /** - * Add a heightfield body to the space. - */ - private void addTerrain() { - // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar - String assetPath = "Textures/Terrain/splat/mountains512.png"; - Texture texture = assetManager.loadTexture(assetPath); - Image image = texture.getImage(); - HeightMap heightMap = new ImageBasedHeightMap(image); - heightMap.setHeightScale(0.2f); - - heightMap.load(); - TerrainQuad terrain - = new TerrainQuad("terrain", 65, 513, heightMap.getHeightMap()); - rootNode.attachChild(terrain); - Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); - terrain.setMaterial(greenMaterial); - - // Construct a static RigidBodyControl based on the HeightMap. - CollisionShape shape = new HeightfieldCollisionShape(heightMap); - RigidBodyControl rbc - = new RigidBodyControl(shape, PhysicsBody.massForStatic); - rbc.setPhysicsSpace(physicsSpace); - terrain.addControl(rbc); - } - - /** - * Configure the Camera during startup. - */ - private void configureCamera() { - flyCam.setMoveSpeed(10f); - - cam.setLocation(new Vector3f(-39f, 34f, -47f)); - cam.setRotation(new Quaternion(0.183f, -0.68302f, 0.183f, 0.68302f)); - } - - /** - * Configure keyboard input during startup. - */ - private void configureInput() { - inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addMapping("walk away", new KeyTrigger(KeyInput.KEY_U)); - inputManager.addMapping("walk left", new KeyTrigger(KeyInput.KEY_H)); - inputManager.addMapping("walk right", new KeyTrigger(KeyInput.KEY_K)); - inputManager.addMapping("walk toward", new KeyTrigger(KeyInput.KEY_J)); - inputManager.addListener(this, - "jump", "walk away", "walk left", "walk right", "walk toward"); - } - - /** - * Configure physics during startup. - * - * @return a new instance (not null) - */ - private PhysicsSpace configurePhysics() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - //bulletAppState.setDebugEnabled(true); // for debug visualization - PhysicsSpace result = bulletAppState.getPhysicsSpace(); - - return result; - } - - /** - * Create a single-sided lit material with the specified reflectivities. - * - * @param red the desired reflectivity for red light (≥0, ≤1) - * @param green the desired reflectivity for green light (≥0, ≤1) - * @param blue the desired reflectivity for blue light (≥0, ≤1) - * @return a new instance (not null) - */ - private Material createLitMaterial(float red, float green, float blue) { - Material result = new Material(assetManager, Materials.LIGHTING); - result.setBoolean("UseMaterialColors", true); - - float opacity = 1f; - result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); - result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); - - return result; - } -} +/* + Copyright (c) 2020-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.tween.action.Action; +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape; +import com.jme3.bullet.control.CharacterControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.HeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; + +/** + * An example of character physics using Oto and CharacterControl. + *

+ * Press the U/H/J/K keys to walk. Press the space bar to jump. + *

+ * Builds upon HelloWalk. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloWalkOtoCc + extends SimpleApplication + implements ActionListener { + // ************************************************************************* + // fields + + private static Action standAction; + private static Action walkAction; + private static AnimComposer composer; + /** + * true when the spacebar is pressed, otherwise false + */ + private static volatile boolean jumpRequested; + /** + * true when the U key is pressed, otherwise false + */ + private static volatile boolean walkAway; + /** + * true when the H key is pressed, otherwise false + */ + private static volatile boolean walkLeft; + /** + * true when the K key is pressed, otherwise false + */ + private static volatile boolean walkRight; + /** + * true when the J key is pressed, otherwise false + */ + private static volatile boolean walkToward; + + private static CharacterControl character; + /** + * PhysicsSpace for simulation + */ + private static PhysicsSpace physicsSpace; + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloWalkOtoCc application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloWalkOtoCc application = new HelloWalkOtoCc(); + + // Enable gamma correction for accurate lighting. + boolean loadDefaults = true; + AppSettings settings = new AppSettings(loadDefaults); + settings.setGammaCorrection(true); + application.setSettings(settings); + + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + addLighting(rootNode); + configureCamera(); + configureInput(); + physicsSpace = configurePhysics(); + + // Load the Oto model and find its animation actions. + Spatial oto = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + composer = oto.getControl(AnimComposer.class); + standAction = composer.action("stand"); + walkAction = composer.action("Walk"); + + rootNode.attachChild(oto); + + // Create the PhysicsControl and add it to the scene and space. + float capsuleRadius = 3f; + float capsuleHeight = 4f; + CapsuleCollisionShape shape + = new CapsuleCollisionShape(capsuleRadius, capsuleHeight); + float stepHeight = 0.01f; + character = new CharacterControl(shape, stepHeight); + character.setGravity(60f); + oto.addControl(character); + physicsSpace.add(character); + + // Teleport the character to its initial location. + character.setPhysicsLocation(new Vector3f(-73.6f, 19.09f, -45.58f)); + + // Add a static heightmap to represent the ground. + addTerrain(); + } + + /** + * Callback invoked once per frame. + * + * @param tpf the time interval between frames (in seconds, ≥0) + */ + @Override + public void simpleUpdate(float tpf) { + // Determine horizontal directions relative to the camera orientation. + Vector3f away = cam.getDirection(); + away.y = 0; + away.normalizeLocal(); + + Vector3f left = cam.getLeft(); + left.y = 0; + left.normalizeLocal(); + + // Determine the walk velocity from keyboard inputs. + Vector3f direction = new Vector3f(); + if (walkAway) { + direction.addLocal(away); + } + if (walkLeft) { + direction.addLocal(left); + } + if (walkRight) { + direction.subtractLocal(left); + } + if (walkToward) { + direction.subtractLocal(away); + } + direction.normalizeLocal(); + float walkSpeed = 7f; + float timeStep = 1 / 60f; + Vector3f walkOffset = direction.mult(walkSpeed * timeStep); + character.setWalkDirection(walkOffset); + + // Decide whether to jump. + if (jumpRequested) { + character.jump(); + } + + // Update the animation action. + Action action = composer.getCurrentAction(); + if (walkOffset.length() < 0.0001f) { + if (action != standAction) { + composer.setCurrentAction("stand"); + } + } else { + character.setViewDirection(direction); + if (action != walkAction) { + composer.setCurrentAction("Walk"); + } + } + } + // ************************************************************************* + // ActionListener methods + + /** + * Callback to handle keyboard input events. + * + * @param action the name of the input event + * @param ongoing true → pressed, false → released + * @param tpf the time per frame (in seconds, ≥0) + */ + @Override + public void onAction(String action, boolean ongoing, float tpf) { + switch (action) { + case "jump": + jumpRequested = ongoing; + return; + + case "walk away": + walkAway = ongoing; + return; + + case "walk left": + walkLeft = ongoing; + return; + + case "walk right": + walkRight = ongoing; + return; + + case "walk toward": + walkToward = ongoing; + return; + + default: + System.out.println("Unknown action: " + action); + } + } + // ************************************************************************* + // private methods + + /** + * Add lighting and shadows to the specified scene and set the background + * color. + * + * @param scene the scene to augment (not null) + */ + private void addLighting(Spatial scene) { + scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + ColorRGBA ambientColor = new ColorRGBA(0.03f, 0.03f, 0.03f, 1f); + AmbientLight ambient = new AmbientLight(ambientColor); + scene.addLight(ambient); + ambient.setName("ambient"); + + ColorRGBA directColor = new ColorRGBA(0.3f, 0.3f, 0.3f, 1f); + Vector3f direction = new Vector3f(-7f, -3f, -5f).normalizeLocal(); + DirectionalLight sun = new DirectionalLight(direction, directColor); + scene.addLight(sun); + sun.setName("sun"); + + // Render shadows based on the directional light. + viewPort.clearProcessors(); + int shadowMapSize = 2_048; // in pixels + int numSplits = 3; + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer( + assetManager, shadowMapSize, numSplits); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + dlsr.setEdgesThickness(5); + dlsr.setLight(sun); + dlsr.setShadowIntensity(0.4f); + viewPort.addProcessor(dlsr); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + } + + /** + * Add a heightfield body to the space. + */ + private void addTerrain() { + // Generate a HeightMap from jme3-testdata-3.1.0-stable.jar + String assetPath = "Textures/Terrain/splat/mountains512.png"; + Texture texture = assetManager.loadTexture(assetPath); + Image image = texture.getImage(); + HeightMap heightMap = new ImageBasedHeightMap(image); + heightMap.setHeightScale(0.2f); + + heightMap.load(); + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightMap.getHeightMap()); + rootNode.attachChild(terrain); + Material greenMaterial = createLitMaterial(0f, 0.5f, 0f); + terrain.setMaterial(greenMaterial); + + // Construct a static RigidBodyControl based on the HeightMap. + CollisionShape shape = new HeightfieldCollisionShape(heightMap); + RigidBodyControl rbc + = new RigidBodyControl(shape, PhysicsBody.massForStatic); + rbc.setPhysicsSpace(physicsSpace); + terrain.addControl(rbc); + } + + /** + * Configure the Camera during startup. + */ + private void configureCamera() { + flyCam.setMoveSpeed(10f); + + cam.setLocation(new Vector3f(-39f, 34f, -47f)); + cam.setRotation(new Quaternion(0.183f, -0.68302f, 0.183f, 0.68302f)); + } + + /** + * Configure keyboard input during startup. + */ + private void configureInput() { + inputManager.addMapping("jump", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("walk away", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("walk left", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("walk right", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("walk toward", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(this, + "jump", "walk away", "walk left", "walk right", "walk toward"); + } + + /** + * Configure physics during startup. + * + * @return a new instance (not null) + */ + private PhysicsSpace configurePhysics() { + BulletAppState bulletAppState = new BulletAppState(); + stateManager.attach(bulletAppState); + //bulletAppState.setDebugEnabled(true); // for debug visualization + PhysicsSpace result = bulletAppState.getPhysicsSpace(); + + return result; + } + + /** + * Create a single-sided lit material with the specified reflectivities. + * + * @param red the desired reflectivity for red light (≥0, ≤1) + * @param green the desired reflectivity for green light (≥0, ≤1) + * @param blue the desired reflectivity for blue light (≥0, ≤1) + * @return a new instance (not null) + */ + private Material createLitMaterial(float red, float green, float blue) { + Material result = new Material(assetManager, Materials.LIGHTING); + result.setBoolean("UseMaterialColors", true); + + float opacity = 1f; + result.setColor("Ambient", new ColorRGBA(red, green, blue, opacity)); + result.setColor("Diffuse", new ColorRGBA(red, green, blue, opacity)); + + return result; + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWind.java b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWind.java index 6c92710f9..896aa4f67 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWind.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/HelloWind.java @@ -1,255 +1,255 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.tutorial; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.PhysicsSoftSpace; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.PhysicsTickListener; -import com.jme3.bullet.SoftPhysicsAppState; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsSoftBody; -import com.jme3.bullet.objects.infos.Aero; -import com.jme3.bullet.objects.infos.Sbcp; -import com.jme3.bullet.objects.infos.SoftBodyConfig; -import com.jme3.bullet.objects.infos.SoftBodyMaterial; -import com.jme3.bullet.util.NativeSoftBodyUtil; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.InputListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import jme3utilities.math.MyMath; -import jme3utilities.mesh.ClothGrid; -import jme3utilities.minie.FilterAll; - -/** - * A simple cloth simulation with wind. - *

- * Builds upon HelloPin. - * - * @author Stephen Gold sgold@sonic.net - */ -public class HelloWind - extends SimpleApplication - implements PhysicsTickListener { - // ************************************************************************* - // constants - - /** - * wind speed, in psu per second - */ - final private static float windSpeed = 3f; - // ************************************************************************* - // fields - - /** - * true when the left-arrow key is pressed, otherwise false - */ - private static volatile boolean turnLeft; - /** - * true when the right-arrow key is pressed, otherwise false - */ - private static volatile boolean turnRight; - /** - * wind direction (in radians from +X) - */ - private static float windAzimuth = -0.8f; - /** - * collision object for the flag - */ - private static PhysicsSoftBody flag; - /** - * temporary storage for velocity vectors - */ - final private static Vector3f tmpVelocity = new Vector3f(); - // ************************************************************************* - // new methods exposed - - /** - * Main entry point for the HelloWind application. - * - * @param arguments array of command-line arguments (not null) - */ - public static void main(String[] arguments) { - HelloWind application = new HelloWind(); - application.start(); - } - // ************************************************************************* - // SimpleApplication methods - - /** - * Initialize the application. - */ - @Override - public void simpleInitApp() { - configureCamera(); - configureInput(); - - // Set the viewport's background color to light blue. - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - viewPort.setBackgroundColor(skyColor); - - // Set up Bullet physics (with debug enabled). - SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); // for debug visualization - bulletAppState.setWindVelocityFilter(new FilterAll(true)); - PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); - - physicsSpace.setAccuracy(0.01f); // 10-msec timestep - - Vector3f gravityVector = new Vector3f(0f, -1f, 0f); - physicsSpace.setGravity(gravityVector); - - // To enable the callbacks, register the application as a tick listener. - physicsSpace.addTickListener(this); - - // Generate a subdivided rectangle mesh with alternating diagonals. - int xLines = 20; - int zLines = 2 * xLines; // 2x as wide as it is tall - float width = 2f; - float lineSpacing = width / zLines; - Mesh mesh = new ClothGrid(xLines, zLines, lineSpacing); - - // Create a soft rectangle for the flag. - flag = new PhysicsSoftBody(); - NativeSoftBodyUtil.appendFromTriMesh(mesh, flag); - flag.setMargin(0.1f); - flag.setMass(1f); - - // Pin the left edge of the flag. - int nodeIndex = 0; // upper left corner - flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic); - nodeIndex = xLines - 1; // lower left corner - flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic); - /* - * Make the flag flexible by reducing the angular stiffness - * of its material. - */ - SoftBodyMaterial softMaterial = flag.getSoftMaterial(); - softMaterial.setAngularStiffness(0f); - - // Configure the flag's aerodynamics. - SoftBodyConfig config = flag.getSoftConfig(); - config.setAerodynamics(Aero.F_TwoSidedLiftDrag); - config.set(Sbcp.Damping, 0.01f); // default = 0 - config.set(Sbcp.Drag, 0.5f); // default = 0.2 - config.set(Sbcp.Lift, 1f); // default = 0 - /* - * Improve simulation accuracy by increasing - * the number of position-solver iterations for the flag. - */ - config.setPositionIterations(3); - - Quaternion rotation - = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); - flag.applyRotation(rotation); - - // Initialize the wind velocity. - tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth); - tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth); - flag.setWindVelocity(tmpVelocity); - - flag.setDebugNumSides(2); - physicsSpace.addCollisionObject(flag); - } - // ************************************************************************* - // PhysicsTickListener methods - - /** - * Callback from Bullet, invoked just before each simulation step. - * - * @param space the space that's about to be stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void prePhysicsTick(PhysicsSpace space, float timeStep) { - // Update the flag's wind velocity. - if (turnLeft) { - windAzimuth -= timeStep; - } - if (turnRight) { - windAzimuth += timeStep; - } - windAzimuth = MyMath.standardizeAngle(windAzimuth); - tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth); - tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth); - flag.setWindVelocity(tmpVelocity); - } - - /** - * Callback from Bullet, invoked just after each simulation step. - * - * @param space the space that was just stepped (not null) - * @param timeStep the time per simulation step (in seconds, ≥0) - */ - @Override - public void physicsTick(PhysicsSpace space, float timeStep) { - // do nothing - } - // ************************************************************************* - // private methods - - /** - * Configure the Camera during startup. - */ - private void configureCamera() { - cam.setLocation(new Vector3f(7f, 1.2f, -0.7f)); - cam.setRotation(new Quaternion(0.08619f, -0.68974f, 0.0833f, 0.71407f)); - } - - /** - * Configure keyboard input during startup. - */ - private void configureInput() { - inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_F1)); - inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_F2)); - InputListener input = new ActionListener() { - @Override - public void onAction(String action, boolean isPressed, float tpf) { - switch (action) { - case "left": - turnLeft = isPressed; - return; - - case "right": - turnRight = isPressed; - return; - - default: - System.out.println("Unknown action: " + action); - } - } - }; - inputManager.addListener(input, "left", "right"); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.tutorial; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSoftSpace; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.SoftPhysicsAppState; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsSoftBody; +import com.jme3.bullet.objects.infos.Aero; +import com.jme3.bullet.objects.infos.Sbcp; +import com.jme3.bullet.objects.infos.SoftBodyConfig; +import com.jme3.bullet.objects.infos.SoftBodyMaterial; +import com.jme3.bullet.util.NativeSoftBodyUtil; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.InputListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import jme3utilities.math.MyMath; +import jme3utilities.mesh.ClothGrid; +import jme3utilities.minie.FilterAll; + +/** + * A simple cloth simulation with wind. + *

+ * Builds upon HelloPin. + * + * @author Stephen Gold sgold@sonic.net + */ +public class HelloWind + extends SimpleApplication + implements PhysicsTickListener { + // ************************************************************************* + // constants + + /** + * wind speed, in psu per second + */ + final private static float windSpeed = 3f; + // ************************************************************************* + // fields + + /** + * true when the left-arrow key is pressed, otherwise false + */ + private static volatile boolean turnLeft; + /** + * true when the right-arrow key is pressed, otherwise false + */ + private static volatile boolean turnRight; + /** + * wind direction (in radians from +X) + */ + private static float windAzimuth = -0.8f; + /** + * collision object for the flag + */ + private static PhysicsSoftBody flag; + /** + * temporary storage for velocity vectors + */ + final private static Vector3f tmpVelocity = new Vector3f(); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the HelloWind application. + * + * @param arguments array of command-line arguments (not null) + */ + public static void main(String[] arguments) { + HelloWind application = new HelloWind(); + application.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + configureCamera(); + configureInput(); + + // Set the viewport's background color to light blue. + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + viewPort.setBackgroundColor(skyColor); + + // Set up Bullet physics (with debug enabled). + SoftPhysicsAppState bulletAppState = new SoftPhysicsAppState(); + stateManager.attach(bulletAppState); + bulletAppState.setDebugEnabled(true); // for debug visualization + bulletAppState.setWindVelocityFilter(new FilterAll(true)); + PhysicsSoftSpace physicsSpace = bulletAppState.getPhysicsSoftSpace(); + + physicsSpace.setAccuracy(0.01f); // 10-msec timestep + + Vector3f gravityVector = new Vector3f(0f, -1f, 0f); + physicsSpace.setGravity(gravityVector); + + // To enable the callbacks, register the application as a tick listener. + physicsSpace.addTickListener(this); + + // Generate a subdivided rectangle mesh with alternating diagonals. + int xLines = 20; + int zLines = 2 * xLines; // 2x as wide as it is tall + float width = 2f; + float lineSpacing = width / zLines; + Mesh mesh = new ClothGrid(xLines, zLines, lineSpacing); + + // Create a soft rectangle for the flag. + flag = new PhysicsSoftBody(); + NativeSoftBodyUtil.appendFromTriMesh(mesh, flag); + flag.setMargin(0.1f); + flag.setMass(1f); + + // Pin the left edge of the flag. + int nodeIndex = 0; // upper left corner + flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic); + nodeIndex = xLines - 1; // lower left corner + flag.setNodeMass(nodeIndex, PhysicsBody.massForStatic); + /* + * Make the flag flexible by reducing the angular stiffness + * of its material. + */ + SoftBodyMaterial softMaterial = flag.getSoftMaterial(); + softMaterial.setAngularStiffness(0f); + + // Configure the flag's aerodynamics. + SoftBodyConfig config = flag.getSoftConfig(); + config.setAerodynamics(Aero.F_TwoSidedLiftDrag); + config.set(Sbcp.Damping, 0.01f); // default = 0 + config.set(Sbcp.Drag, 0.5f); // default = 0.2 + config.set(Sbcp.Lift, 1f); // default = 0 + /* + * Improve simulation accuracy by increasing + * the number of position-solver iterations for the flag. + */ + config.setPositionIterations(3); + + Quaternion rotation + = new Quaternion().fromAngles(FastMath.HALF_PI, 0f, 0f); + flag.applyRotation(rotation); + + // Initialize the wind velocity. + tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth); + tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth); + flag.setWindVelocity(tmpVelocity); + + flag.setDebugNumSides(2); + physicsSpace.addCollisionObject(flag); + } + // ************************************************************************* + // PhysicsTickListener methods + + /** + * Callback from Bullet, invoked just before each simulation step. + * + * @param space the space that's about to be stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void prePhysicsTick(PhysicsSpace space, float timeStep) { + // Update the flag's wind velocity. + if (turnLeft) { + windAzimuth -= timeStep; + } + if (turnRight) { + windAzimuth += timeStep; + } + windAzimuth = MyMath.standardizeAngle(windAzimuth); + tmpVelocity.x = windSpeed * FastMath.cos(windAzimuth); + tmpVelocity.z = windSpeed * FastMath.sin(windAzimuth); + flag.setWindVelocity(tmpVelocity); + } + + /** + * Callback from Bullet, invoked just after each simulation step. + * + * @param space the space that was just stepped (not null) + * @param timeStep the time per simulation step (in seconds, ≥0) + */ + @Override + public void physicsTick(PhysicsSpace space, float timeStep) { + // do nothing + } + // ************************************************************************* + // private methods + + /** + * Configure the Camera during startup. + */ + private void configureCamera() { + cam.setLocation(new Vector3f(7f, 1.2f, -0.7f)); + cam.setRotation(new Quaternion(0.08619f, -0.68974f, 0.0833f, 0.71407f)); + } + + /** + * Configure keyboard input during startup. + */ + private void configureInput() { + inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_F1)); + inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_F2)); + InputListener input = new ActionListener() { + @Override + public void onAction(String action, boolean isPressed, float tpf) { + switch (action) { + case "left": + turnLeft = isPressed; + return; + + case "right": + turnRight = isPressed; + return; + + default: + System.out.println("Unknown action: " + action); + } + } + }; + inputManager.addListener(input, "left", "right"); + } +} diff --git a/TutorialApps/src/main/java/jme3utilities/tutorial/package-info.java b/TutorialApps/src/main/java/jme3utilities/tutorial/package-info.java index 63c66914c..1b151d3d8 100644 --- a/TutorialApps/src/main/java/jme3utilities/tutorial/package-info.java +++ b/TutorialApps/src/main/java/jme3utilities/tutorial/package-info.java @@ -1,31 +1,31 @@ -/* - Copyright (c) 2019-2020, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * Short/simple code examples, intended to help beginners learn how to use - * Minie. - */ -package jme3utilities.tutorial; +/* + Copyright (c) 2019-2020, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * Short/simple code examples, intended to help beginners learn how to use + * Minie. + */ +package jme3utilities.tutorial; diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Action.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Action.java index 5a14b1f79..ec594c62d 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Action.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Action.java @@ -1,165 +1,165 @@ -/* - Copyright (c) 2019-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.renderer.RenderManager; -import java.util.logging.Logger; -import jme3utilities.nifty.bind.BindScreen; -import jme3utilities.nifty.displaysettings.DsScreen; -import jme3utilities.ui.InputMode; - -/** - * Action strings for the VhacdTuner application. Each String describes a - * user-interface action. By convention, action strings begin with a verb in all - * lowercase and never end with a space (' '). - * - * @author Stephen Gold sgold@sonic.net - */ -final class Action { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(Action.class.getName()); - /** - * action strings used by multiple input modes: - */ - final static String dumpAppStates = "dump appStates"; - final static String dumpPhysicsSpace = "dump physicsSpace"; - final static String dumpRenderer = "dump renderer"; - final static String editBindings = "edit bindings"; - final static String editDisplaySettings = "edit displaySettings"; - final static String nextScreen = "next screen"; - final static String previousScreen = "previous screen"; - final static String toggleAxes = "toggle axes"; - final static String toggleMesh = "toggle mesh"; - final static String togglePhysicsDebug = "toggle physicsDebug"; - // ************************************************************************* - // constructors - - /** - * A private constructor to inhibit instantiation of this class. - */ - private Action() { - // do nothing - } - // ************************************************************************* - // new methods exposed - - /** - * Process an ongoing action from the GUI or keyboard that wasn't handled by - * the active InputMode. - * - * @param actionString textual description of the action (not null) - * @return true if the action has been handled, otherwise false - */ - static boolean processOngoing(String actionString) { - boolean handled = false; - switch (actionString) { - case dumpAppStates: - dumpAppStates(); - handled = true; - break; - - case dumpPhysicsSpace: - dumpPhysicsSpace(); - handled = true; - break; - - case dumpRenderer: - dumpRenderer(); - handled = true; - break; - - case editBindings: - editBindings(); - handled = true; - break; - - case editDisplaySettings: - editDisplaySettings(); - handled = true; - break; - - default: - } - - return handled; - } - // ************************************************************************* - // private methods - - /** - * Process a "dump appStates" action. - */ - private static void dumpAppStates() { - VhacdTuner app = VhacdTuner.getApplication(); - AppStateManager stateManager = app.getStateManager(); - VhacdTuner.dumper.dump(stateManager); - } - - /** - * Process a "dump physicsSpace" action. - */ - private static void dumpPhysicsSpace() { - PhysicsSpace leftSpace = VhacdTuner.getLeftSpace(); - VhacdTuner.dumper.dump(leftSpace); - - PhysicsSpace rightSpace = VhacdTuner.getRightSpace(); - VhacdTuner.dumper.dump(rightSpace); - } - - /** - * Process a "dump renderer" action. - */ - private static void dumpRenderer() { - VhacdTuner app = VhacdTuner.getApplication(); - RenderManager renderManager = app.getRenderManager(); - VhacdTuner.dumper.dump(renderManager); - } - - /** - * Process an "edit bindings" action. - */ - private static void editBindings() { - BindScreen bindScreen = VhacdTuner.findAppState(BindScreen.class); - InputMode active = InputMode.getActiveMode(); - bindScreen.activate(active); - } - - /** - * Process an "edit displaySettings" action. - */ - private static void editDisplaySettings() { - DsScreen dss = VhacdTuner.findAppState(DsScreen.class); - dss.activate(); - } -} +/* + Copyright (c) 2019-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.renderer.RenderManager; +import java.util.logging.Logger; +import jme3utilities.nifty.bind.BindScreen; +import jme3utilities.nifty.displaysettings.DsScreen; +import jme3utilities.ui.InputMode; + +/** + * Action strings for the VhacdTuner application. Each String describes a + * user-interface action. By convention, action strings begin with a verb in all + * lowercase and never end with a space (' '). + * + * @author Stephen Gold sgold@sonic.net + */ +final class Action { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(Action.class.getName()); + /** + * action strings used by multiple input modes: + */ + final static String dumpAppStates = "dump appStates"; + final static String dumpPhysicsSpace = "dump physicsSpace"; + final static String dumpRenderer = "dump renderer"; + final static String editBindings = "edit bindings"; + final static String editDisplaySettings = "edit displaySettings"; + final static String nextScreen = "next screen"; + final static String previousScreen = "previous screen"; + final static String toggleAxes = "toggle axes"; + final static String toggleMesh = "toggle mesh"; + final static String togglePhysicsDebug = "toggle physicsDebug"; + // ************************************************************************* + // constructors + + /** + * A private constructor to inhibit instantiation of this class. + */ + private Action() { + // do nothing + } + // ************************************************************************* + // new methods exposed + + /** + * Process an ongoing action from the GUI or keyboard that wasn't handled by + * the active InputMode. + * + * @param actionString textual description of the action (not null) + * @return true if the action has been handled, otherwise false + */ + static boolean processOngoing(String actionString) { + boolean handled = false; + switch (actionString) { + case dumpAppStates: + dumpAppStates(); + handled = true; + break; + + case dumpPhysicsSpace: + dumpPhysicsSpace(); + handled = true; + break; + + case dumpRenderer: + dumpRenderer(); + handled = true; + break; + + case editBindings: + editBindings(); + handled = true; + break; + + case editDisplaySettings: + editDisplaySettings(); + handled = true; + break; + + default: + } + + return handled; + } + // ************************************************************************* + // private methods + + /** + * Process a "dump appStates" action. + */ + private static void dumpAppStates() { + VhacdTuner app = VhacdTuner.getApplication(); + AppStateManager stateManager = app.getStateManager(); + VhacdTuner.dumper.dump(stateManager); + } + + /** + * Process a "dump physicsSpace" action. + */ + private static void dumpPhysicsSpace() { + PhysicsSpace leftSpace = VhacdTuner.getLeftSpace(); + VhacdTuner.dumper.dump(leftSpace); + + PhysicsSpace rightSpace = VhacdTuner.getRightSpace(); + VhacdTuner.dumper.dump(rightSpace); + } + + /** + * Process a "dump renderer" action. + */ + private static void dumpRenderer() { + VhacdTuner app = VhacdTuner.getApplication(); + RenderManager renderManager = app.getRenderManager(); + VhacdTuner.dumper.dump(renderManager); + } + + /** + * Process an "edit bindings" action. + */ + private static void editBindings() { + BindScreen bindScreen = VhacdTuner.findAppState(BindScreen.class); + InputMode active = InputMode.getActiveMode(); + bindScreen.activate(active); + } + + /** + * Process an "edit displaySettings" action. + */ + private static void editDisplaySettings() { + DsScreen dss = VhacdTuner.findAppState(DsScreen.class); + dss.activate(); + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/DecompositionTest.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/DecompositionTest.java index bd77da6b0..d7e982d16 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/DecompositionTest.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/DecompositionTest.java @@ -1,657 +1,657 @@ -/* - Copyright (c) 2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.HullCollisionShape; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.scene.Spatial; -import java.io.PrintStream; -import java.util.Map; -import java.util.logging.Logger; -import jme3utilities.Validate; -import vhacd.VHACDParameters; -import vhacd4.Vhacd4Parameters; - -/** - * The parameters of a V-HACD test plus the corresponding results. - * - * @author Stephen Gold sgold@sonic.net - */ -final class DecompositionTest implements Runnable { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger - = Logger.getLogger(DecompositionTest.class.getName()); - // ************************************************************************* - // fields - - /** - * output of the test, or null if not run yet - */ - private CompoundCollisionShape shape; - /** - * run time of the test (in seconds) - */ - private float latency; - /** - * 0 before running, 1 while running, 2 when done - */ - private int runState = 0; - /** - * total number of vertices in the output, or null if not yet calculated - */ - private Integer totalVertices; - /** - * input parameters for V-HACD v4, or null for classic - */ - final private Vhacd4Parameters v4; - /** - * input parameters for classic V-HACD, or null for v4 - */ - final private VHACDParameters classic; - // ************************************************************************* - // constructors - - /** - * Instantiate a V-HACD v4 test with the specified parameters. - * - * @param parameters (not null, unaffected) - */ - DecompositionTest(Vhacd4Parameters parameters) { - Validate.nonNull(parameters, "parameters"); - - this.v4 = parameters.clone(); - this.classic = null; - } - - /** - * Instantiate a classic V-HACD test with the specified parameters. - * - * @param parameters (not null, unaffected) - */ - DecompositionTest(VHACDParameters parameters) { - Validate.nonNull(parameters, "parameters"); - - this.v4 = null; - this.classic = parameters.clone(); - } - // ************************************************************************* - // new methods exposed - - /** - * Return a copy of the parameters, assuming classic V-HACD. - * - * @return the pre-existing instance (not null) - */ - VHACDParameters copyClassic() { - VHACDParameters result = classic.clone(); - return result; - } - - /** - * Return a copy of the parameters, assuming V-HACD v4. - * - * @return a new instance (not null) - */ - Vhacd4Parameters copyV4() { - Vhacd4Parameters result = v4.clone(); - return result; - } - - /** - * Count the vertices in the output, assuming the test has been run. - * - * @return the pre-existing instance (not null) - */ - int countVertices() { - assert hasBeenRun(); - if (totalVertices == null) { - calculateTotalVertices(); - } - - return totalVertices; - } - - /** - * Return the output of this test, assuming it has been run. - * - * @return the pre-existing instance (not null) - */ - CompoundCollisionShape getShape() { - assert hasBeenRun(); - return shape; - } - - /** - * Determine whether this test has been run. - * - * @return true if run, otherwise false - */ - boolean hasBeenRun() { - if (runState == 2) { - return true; - } else { - return false; - } - } - - /** - * Test whether classic V-HACD was/will be used. - * - * @return true for classic, false for v4 - */ - boolean isClassic() { - if (classic == null) { - return false; - } else { - return true; - } - } - - /** - * Test whether the test is currently running. - * - * @return true if running, otherwise false - */ - boolean isRunning() { - if (runState == 1) { - return true; - } else { - return false; - } - } - - /** - * Return the latency of the test, assuming it has been run. - * - * @return the latency (in seconds, ≥0) - */ - float latency() { - assert hasBeenRun(); - assert latency >= 0f : latency; - return latency; - } - - /** - * Return the maximum vertices per hull. - * - * @return the value (≥4, ≤2_048) - */ - int maxVerticesPerHull() { - int result; - - if (classic == null) { - result = v4.getMaxVerticesPerHull(); - } else { - result = classic.getMaxVerticesPerHull(); - } - - assert result >= 4 : result; - assert result <= 2_048 : result; - return result; - } - - /** - * Return the voxel resolution. - * - * @return the value (≥10_000, ≤64_000_000) - */ - int resolution() { - int result; - - if (classic == null) { - result = v4.getVoxelResolution(); - } else { - result = classic.getVoxelResolution(); - } - - assert result >= 10_000 : result; - assert result <= 64_000_000 : result; - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * alpha. - * - * @param alpha the desired alpha (≥0, ≤1) - * @return a new or pre-existing test - */ - DecompositionTest setAlpha(double alpha) { - Validate.fraction(alpha, "alpha"); - - VHACDParameters copy = copyClassic(); - copy.setAlpha(alpha); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified beta. - * - * @param beta the desired beta (≥0, ≤1) - * @return a new or pre-existing test - */ - DecompositionTest setBeta(double beta) { - Validate.fraction(beta, "beta"); - - VHACDParameters copy = copyClassic(); - copy.setBeta(beta); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * convex-hull downsampling. - * - * @param precision the desired precision (≥1, ≤16) - * @return a new or pre-existing test - */ - DecompositionTest setHullDS(int precision) { - Validate.inRange(precision, "precision", 1, 16); - - VHACDParameters copy = copyClassic(); - copy.setConvexHullDownSampling(precision); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * maximum concavity. - * - * @param concavity the desired concavity (≥0, ≤1) - * @return a new or pre-existing test - */ - DecompositionTest setMaxConcavity(double concavity) { - Validate.fraction(concavity, "concavity"); - - VHACDParameters copy = copyClassic(); - copy.setMaxConcavity(concavity); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * maximum number of hulls. - * - * @param limit the desired maximum number (≥1, ≤1_024) - * @return a new or pre-existing test - */ - DecompositionTest setMaxHulls(int limit) { - Validate.inRange(limit, "limit", 1, 1_024); - - Vhacd4Parameters copy = copyV4(); - copy.setMaxHulls(limit); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * maximum recursion. - * - * @param depth the desired maximum depth (≥2, ≤64) - * @return a new or pre-existing test - */ - DecompositionTest setMaxRecursion(int depth) { - Validate.inRange(depth, "depth", 2, 64); - - Vhacd4Parameters copy = copyV4(); - copy.setMaxRecursion(depth); - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * maximum vertices per hull. - * - * @param limit the desired maximum number (≥4, ≤2_048) - * @return a new or pre-existing test - */ - DecompositionTest setMaxVerticesPerHull(int limit) { - Validate.inRange(limit, "limit", 4, 2_048); - - Model model = VhacdTuner.getModel(); - DecompositionTest result; - if (classic == null) { - Vhacd4Parameters copy = copyV4(); - copy.setMaxVerticesPerHull(limit); - result = model.getTest(copy); - } else { - VHACDParameters copy = copyClassic(); - copy.setMaxVerticesPerHull(limit); - result = model.getTest(copy); - } - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * minimum edge length. - * - * @param length the desired minimum length (≥1, ≤32) - * @return a new or pre-existing test - */ - DecompositionTest setMinEdgeLength(int length) { - Validate.inRange(length, "length", 1, 32); - - Vhacd4Parameters copy = copyV4(); - copy.setMinEdgeLength(length); - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified min - * volume per hull. - * - * @param volume the desired volume (≥0, ≤0.1) - * @return a new or pre-existing test - */ - DecompositionTest setMinVolumePH(double volume) { - Validate.inRange(volume, "volume", 0.0, 0.1); - - VHACDParameters copy = copyClassic(); - copy.setMinVolumePerHull(volume); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified plane - * downsampling. - * - * @param precision the desired precision (≥1, ≤16) - * @return a new or pre-existing test - */ - DecompositionTest setPlaneDS(int precision) { - Validate.inRange(precision, "precision", 1, 16); - - VHACDParameters copy = copyClassic(); - copy.setPlaneDownSampling(precision); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * resolution. - * - * @param maxVoxels the desired resolution (≥10_000, ≤64_000_000) - * @return a new or pre-existing test - */ - DecompositionTest setResolution(int maxVoxels) { - Validate.inRange(maxVoxels, "maxVoxels", 10_000, 64_000_000); - - Model model = VhacdTuner.getModel(); - DecompositionTest result; - if (classic == null) { - Vhacd4Parameters copy = copyV4(); - copy.setVoxelResolution(maxVoxels); - result = model.getTest(copy); - } else { - VHACDParameters copy = copyClassic(); - copy.setVoxelResolution(maxVoxels); - result = model.getTest(copy); - } - - return result; - } - - /** - * Create or find a test identical to this one, but with the specified - * volume percent error. - * - * @param percentage the desired percent error (≥0, ≤100) - * @return a new or pre-existing test - */ - DecompositionTest setVolumePercentError(double percentage) { - Validate.inRange(percentage, "percentage", 0.0, 100.0); - - Vhacd4Parameters copy = copyV4(); - copy.setVolumePercentError(percentage); - - Model model = VhacdTuner.getModel(); - DecompositionTest result = model.getTest(copy); - - return result; - } - - /** - * Represent this instance as a Map, in order to make comparisons easier. - * - * @return a map of property names to values - */ - Map toMap() { - Map result; - if (classic == null) { - result = v4.toMap(); - } else { - result = classic.toMap(); - } - result.put("classic", isClassic()); - result.put("runState", runState); - if (runState == 2) { - int numHulls = shape.countChildren(); - result.put("hulls", numHulls); - result.put("seconds", latency); - result.put("vertices", totalVertices); - } - - return result; - } - - /** - * Write the V-HACD parameters to a stream, as Java source code. - * - * @param stream the output stream (not null) - */ - void write(PrintStream stream) { - if (v4 == null) { - stream.printf("import vhacd.VHACDParameters;%n%n" - + "public class WParameters extends VHACDParameters {%n%n" - + " public WParameters() {%n" - ); - stream.printf(" setACDMode(ACDMode.%s);%n", - classic.getACDMode()); - stream.printf(" setAlpha(%s);%n", - classic.getAlpha()); - stream.printf(" setBeta(%s);%n", - classic.getBeta()); - stream.printf(" setConvexHullDownSampling(%s);%n", - classic.getConvexHullDownSampling()); - stream.printf(" setDebugEnabled(%s);%n", - classic.getDebugEnabled()); - stream.printf(" setMaxConcavity(%s);%n", - classic.getMaxConcavity()); - stream.printf(" setMaxVerticesPerHull(%s);%n", - classic.getMaxVerticesPerHull()); - stream.printf(" setMinVolumePerHull(%s);%n", - classic.getMinVolumePerHull()); - stream.printf(" setPCA(%s);%n", - classic.getPCA()); - stream.printf(" setPlaneDownSampling(%s);%n", - classic.getPlaneDownSampling()); - stream.printf(" setVoxelResolution(%s);%n", - classic.getVoxelResolution()); - stream.printf(" }%n}%n"); - - } else { - stream.printf("import vhacd.Vhacd4Parameters;%n%n" - + "public class WParameters extends Vhacd4Parameters {%n%n" - + " public WParameters() {%n" - ); - stream.printf(" setAsync(%s);%n", - v4.isAsync()); - stream.printf(" setFillMode(FillMode.%s);%n", - v4.getFillMode()); - stream.printf(" setFindBestPlane(%s);%n, ", - v4.isFindBestPlane()); - stream.printf(" setMaxHulls(%s);%n", - v4.getMaxHulls()); - stream.printf(" setMaxRecursion(%s);%n", - v4.getMaxRecursion()); - stream.printf(" setMaxVerticesPerHull(%s);%n", - v4.getMaxVerticesPerHull()); - stream.printf(" setMinEdgeLength(%s);%n", - v4.getMinEdgeLength()); - stream.printf(" setShrinkWrap(%s);%n", - v4.isShrinkWrap()); - stream.printf(" setVolumePercentError(%s);%n", - v4.getVolumePercentError()); - stream.printf(" setVoxelResolution(%s);%n", - v4.getVoxelResolution()); - stream.printf(" }%n}%n"); - } - } - // ************************************************************************* - // Runnable methods - - /** - * Compute the collision shape using V-HACD. - */ - @Override - public void run() { - assert !hasBeenRun(); - this.runState = 1; - - Model model = VhacdTuner.getModel(); - Spatial modelRoot = model.getRootSpatial(); - CompoundCollisionShape collisionShape; - - long startTime = System.nanoTime(); - if (v4 == null) { - collisionShape = CollisionShapeFactory.createVhacdShape( - modelRoot, classic, null); - } else { - collisionShape = CollisionShapeFactory.createVhacdShape( - modelRoot, v4, null); - } - - long elapsedNanoseconds = System.nanoTime() - startTime; - assert elapsedNanoseconds >= 0L : elapsedNanoseconds; - float elapsedSeconds = elapsedNanoseconds * 1e-9f; - assert elapsedSeconds >= 0f : elapsedSeconds; - - assert collisionShape != null; - this.shape = collisionShape; - this.latency = elapsedSeconds; - - this.runState = 2; // Do this last! - } - // ************************************************************************* - // Object methods - - /** - * Represent this instance as a String. - * - * @return a descriptive string of text (not null, not empty) - */ - @Override - public String toString() { - String result; - if (classic == null) { - result = v4.toString(); - } else { - result = classic.toString(); - } - - switch (runState) { - case 0: - result += " not started"; - break; - - case 1: - result += " running"; - break; - - case 2: - int numHulls = shape.countChildren(); - result = String.format("%s -> %f sec, %d hulls, %d vertices", - result, latency, numHulls, totalVertices); - break; - - default: - throw new IllegalStateException("runState = " + runState); - } - - return result; - } - // ************************************************************************* - // private methods - - /** - * Calculate the total number of vertices in the output shape. - */ - private void calculateTotalVertices() { - int sum = 0; - ChildCollisionShape[] children = shape.listChildren(); - for (ChildCollisionShape child : children) { - CollisionShape childShape = child.getShape(); - HullCollisionShape hull = (HullCollisionShape) childShape; - sum += hull.countHullVertices(); - } - this.totalVertices = sum; - } -} +/* + Copyright (c) 2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.CompoundCollisionShape; +import com.jme3.bullet.collision.shapes.HullCollisionShape; +import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; +import com.jme3.bullet.util.CollisionShapeFactory; +import com.jme3.scene.Spatial; +import java.io.PrintStream; +import java.util.Map; +import java.util.logging.Logger; +import jme3utilities.Validate; +import vhacd.VHACDParameters; +import vhacd4.Vhacd4Parameters; + +/** + * The parameters of a V-HACD test plus the corresponding results. + * + * @author Stephen Gold sgold@sonic.net + */ +final class DecompositionTest implements Runnable { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger + = Logger.getLogger(DecompositionTest.class.getName()); + // ************************************************************************* + // fields + + /** + * output of the test, or null if not run yet + */ + private CompoundCollisionShape shape; + /** + * run time of the test (in seconds) + */ + private float latency; + /** + * 0 before running, 1 while running, 2 when done + */ + private int runState = 0; + /** + * total number of vertices in the output, or null if not yet calculated + */ + private Integer totalVertices; + /** + * input parameters for V-HACD v4, or null for classic + */ + final private Vhacd4Parameters v4; + /** + * input parameters for classic V-HACD, or null for v4 + */ + final private VHACDParameters classic; + // ************************************************************************* + // constructors + + /** + * Instantiate a V-HACD v4 test with the specified parameters. + * + * @param parameters (not null, unaffected) + */ + DecompositionTest(Vhacd4Parameters parameters) { + Validate.nonNull(parameters, "parameters"); + + this.v4 = parameters.clone(); + this.classic = null; + } + + /** + * Instantiate a classic V-HACD test with the specified parameters. + * + * @param parameters (not null, unaffected) + */ + DecompositionTest(VHACDParameters parameters) { + Validate.nonNull(parameters, "parameters"); + + this.v4 = null; + this.classic = parameters.clone(); + } + // ************************************************************************* + // new methods exposed + + /** + * Return a copy of the parameters, assuming classic V-HACD. + * + * @return the pre-existing instance (not null) + */ + VHACDParameters copyClassic() { + VHACDParameters result = classic.clone(); + return result; + } + + /** + * Return a copy of the parameters, assuming V-HACD v4. + * + * @return a new instance (not null) + */ + Vhacd4Parameters copyV4() { + Vhacd4Parameters result = v4.clone(); + return result; + } + + /** + * Count the vertices in the output, assuming the test has been run. + * + * @return the pre-existing instance (not null) + */ + int countVertices() { + assert hasBeenRun(); + if (totalVertices == null) { + calculateTotalVertices(); + } + + return totalVertices; + } + + /** + * Return the output of this test, assuming it has been run. + * + * @return the pre-existing instance (not null) + */ + CompoundCollisionShape getShape() { + assert hasBeenRun(); + return shape; + } + + /** + * Determine whether this test has been run. + * + * @return true if run, otherwise false + */ + boolean hasBeenRun() { + if (runState == 2) { + return true; + } else { + return false; + } + } + + /** + * Test whether classic V-HACD was/will be used. + * + * @return true for classic, false for v4 + */ + boolean isClassic() { + if (classic == null) { + return false; + } else { + return true; + } + } + + /** + * Test whether the test is currently running. + * + * @return true if running, otherwise false + */ + boolean isRunning() { + if (runState == 1) { + return true; + } else { + return false; + } + } + + /** + * Return the latency of the test, assuming it has been run. + * + * @return the latency (in seconds, ≥0) + */ + float latency() { + assert hasBeenRun(); + assert latency >= 0f : latency; + return latency; + } + + /** + * Return the maximum vertices per hull. + * + * @return the value (≥4, ≤2_048) + */ + int maxVerticesPerHull() { + int result; + + if (classic == null) { + result = v4.getMaxVerticesPerHull(); + } else { + result = classic.getMaxVerticesPerHull(); + } + + assert result >= 4 : result; + assert result <= 2_048 : result; + return result; + } + + /** + * Return the voxel resolution. + * + * @return the value (≥10_000, ≤64_000_000) + */ + int resolution() { + int result; + + if (classic == null) { + result = v4.getVoxelResolution(); + } else { + result = classic.getVoxelResolution(); + } + + assert result >= 10_000 : result; + assert result <= 64_000_000 : result; + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * alpha. + * + * @param alpha the desired alpha (≥0, ≤1) + * @return a new or pre-existing test + */ + DecompositionTest setAlpha(double alpha) { + Validate.fraction(alpha, "alpha"); + + VHACDParameters copy = copyClassic(); + copy.setAlpha(alpha); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified beta. + * + * @param beta the desired beta (≥0, ≤1) + * @return a new or pre-existing test + */ + DecompositionTest setBeta(double beta) { + Validate.fraction(beta, "beta"); + + VHACDParameters copy = copyClassic(); + copy.setBeta(beta); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * convex-hull downsampling. + * + * @param precision the desired precision (≥1, ≤16) + * @return a new or pre-existing test + */ + DecompositionTest setHullDS(int precision) { + Validate.inRange(precision, "precision", 1, 16); + + VHACDParameters copy = copyClassic(); + copy.setConvexHullDownSampling(precision); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * maximum concavity. + * + * @param concavity the desired concavity (≥0, ≤1) + * @return a new or pre-existing test + */ + DecompositionTest setMaxConcavity(double concavity) { + Validate.fraction(concavity, "concavity"); + + VHACDParameters copy = copyClassic(); + copy.setMaxConcavity(concavity); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * maximum number of hulls. + * + * @param limit the desired maximum number (≥1, ≤1_024) + * @return a new or pre-existing test + */ + DecompositionTest setMaxHulls(int limit) { + Validate.inRange(limit, "limit", 1, 1_024); + + Vhacd4Parameters copy = copyV4(); + copy.setMaxHulls(limit); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * maximum recursion. + * + * @param depth the desired maximum depth (≥2, ≤64) + * @return a new or pre-existing test + */ + DecompositionTest setMaxRecursion(int depth) { + Validate.inRange(depth, "depth", 2, 64); + + Vhacd4Parameters copy = copyV4(); + copy.setMaxRecursion(depth); + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * maximum vertices per hull. + * + * @param limit the desired maximum number (≥4, ≤2_048) + * @return a new or pre-existing test + */ + DecompositionTest setMaxVerticesPerHull(int limit) { + Validate.inRange(limit, "limit", 4, 2_048); + + Model model = VhacdTuner.getModel(); + DecompositionTest result; + if (classic == null) { + Vhacd4Parameters copy = copyV4(); + copy.setMaxVerticesPerHull(limit); + result = model.getTest(copy); + } else { + VHACDParameters copy = copyClassic(); + copy.setMaxVerticesPerHull(limit); + result = model.getTest(copy); + } + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * minimum edge length. + * + * @param length the desired minimum length (≥1, ≤32) + * @return a new or pre-existing test + */ + DecompositionTest setMinEdgeLength(int length) { + Validate.inRange(length, "length", 1, 32); + + Vhacd4Parameters copy = copyV4(); + copy.setMinEdgeLength(length); + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified min + * volume per hull. + * + * @param volume the desired volume (≥0, ≤0.1) + * @return a new or pre-existing test + */ + DecompositionTest setMinVolumePH(double volume) { + Validate.inRange(volume, "volume", 0.0, 0.1); + + VHACDParameters copy = copyClassic(); + copy.setMinVolumePerHull(volume); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified plane + * downsampling. + * + * @param precision the desired precision (≥1, ≤16) + * @return a new or pre-existing test + */ + DecompositionTest setPlaneDS(int precision) { + Validate.inRange(precision, "precision", 1, 16); + + VHACDParameters copy = copyClassic(); + copy.setPlaneDownSampling(precision); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * resolution. + * + * @param maxVoxels the desired resolution (≥10_000, ≤64_000_000) + * @return a new or pre-existing test + */ + DecompositionTest setResolution(int maxVoxels) { + Validate.inRange(maxVoxels, "maxVoxels", 10_000, 64_000_000); + + Model model = VhacdTuner.getModel(); + DecompositionTest result; + if (classic == null) { + Vhacd4Parameters copy = copyV4(); + copy.setVoxelResolution(maxVoxels); + result = model.getTest(copy); + } else { + VHACDParameters copy = copyClassic(); + copy.setVoxelResolution(maxVoxels); + result = model.getTest(copy); + } + + return result; + } + + /** + * Create or find a test identical to this one, but with the specified + * volume percent error. + * + * @param percentage the desired percent error (≥0, ≤100) + * @return a new or pre-existing test + */ + DecompositionTest setVolumePercentError(double percentage) { + Validate.inRange(percentage, "percentage", 0.0, 100.0); + + Vhacd4Parameters copy = copyV4(); + copy.setVolumePercentError(percentage); + + Model model = VhacdTuner.getModel(); + DecompositionTest result = model.getTest(copy); + + return result; + } + + /** + * Represent this instance as a Map, in order to make comparisons easier. + * + * @return a map of property names to values + */ + Map toMap() { + Map result; + if (classic == null) { + result = v4.toMap(); + } else { + result = classic.toMap(); + } + result.put("classic", isClassic()); + result.put("runState", runState); + if (runState == 2) { + int numHulls = shape.countChildren(); + result.put("hulls", numHulls); + result.put("seconds", latency); + result.put("vertices", totalVertices); + } + + return result; + } + + /** + * Write the V-HACD parameters to a stream, as Java source code. + * + * @param stream the output stream (not null) + */ + void write(PrintStream stream) { + if (v4 == null) { + stream.printf("import vhacd.VHACDParameters;%n%n" + + "public class WParameters extends VHACDParameters {%n%n" + + " public WParameters() {%n" + ); + stream.printf(" setACDMode(ACDMode.%s);%n", + classic.getACDMode()); + stream.printf(" setAlpha(%s);%n", + classic.getAlpha()); + stream.printf(" setBeta(%s);%n", + classic.getBeta()); + stream.printf(" setConvexHullDownSampling(%s);%n", + classic.getConvexHullDownSampling()); + stream.printf(" setDebugEnabled(%s);%n", + classic.getDebugEnabled()); + stream.printf(" setMaxConcavity(%s);%n", + classic.getMaxConcavity()); + stream.printf(" setMaxVerticesPerHull(%s);%n", + classic.getMaxVerticesPerHull()); + stream.printf(" setMinVolumePerHull(%s);%n", + classic.getMinVolumePerHull()); + stream.printf(" setPCA(%s);%n", + classic.getPCA()); + stream.printf(" setPlaneDownSampling(%s);%n", + classic.getPlaneDownSampling()); + stream.printf(" setVoxelResolution(%s);%n", + classic.getVoxelResolution()); + stream.printf(" }%n}%n"); + + } else { + stream.printf("import vhacd.Vhacd4Parameters;%n%n" + + "public class WParameters extends Vhacd4Parameters {%n%n" + + " public WParameters() {%n" + ); + stream.printf(" setAsync(%s);%n", + v4.isAsync()); + stream.printf(" setFillMode(FillMode.%s);%n", + v4.getFillMode()); + stream.printf(" setFindBestPlane(%s);%n, ", + v4.isFindBestPlane()); + stream.printf(" setMaxHulls(%s);%n", + v4.getMaxHulls()); + stream.printf(" setMaxRecursion(%s);%n", + v4.getMaxRecursion()); + stream.printf(" setMaxVerticesPerHull(%s);%n", + v4.getMaxVerticesPerHull()); + stream.printf(" setMinEdgeLength(%s);%n", + v4.getMinEdgeLength()); + stream.printf(" setShrinkWrap(%s);%n", + v4.isShrinkWrap()); + stream.printf(" setVolumePercentError(%s);%n", + v4.getVolumePercentError()); + stream.printf(" setVoxelResolution(%s);%n", + v4.getVoxelResolution()); + stream.printf(" }%n}%n"); + } + } + // ************************************************************************* + // Runnable methods + + /** + * Compute the collision shape using V-HACD. + */ + @Override + public void run() { + assert !hasBeenRun(); + this.runState = 1; + + Model model = VhacdTuner.getModel(); + Spatial modelRoot = model.getRootSpatial(); + CompoundCollisionShape collisionShape; + + long startTime = System.nanoTime(); + if (v4 == null) { + collisionShape = CollisionShapeFactory.createVhacdShape( + modelRoot, classic, null); + } else { + collisionShape = CollisionShapeFactory.createVhacdShape( + modelRoot, v4, null); + } + + long elapsedNanoseconds = System.nanoTime() - startTime; + assert elapsedNanoseconds >= 0L : elapsedNanoseconds; + float elapsedSeconds = elapsedNanoseconds * 1e-9f; + assert elapsedSeconds >= 0f : elapsedSeconds; + + assert collisionShape != null; + this.shape = collisionShape; + this.latency = elapsedSeconds; + + this.runState = 2; // Do this last! + } + // ************************************************************************* + // Object methods + + /** + * Represent this instance as a String. + * + * @return a descriptive string of text (not null, not empty) + */ + @Override + public String toString() { + String result; + if (classic == null) { + result = v4.toString(); + } else { + result = classic.toString(); + } + + switch (runState) { + case 0: + result += " not started"; + break; + + case 1: + result += " running"; + break; + + case 2: + int numHulls = shape.countChildren(); + result = String.format("%s -> %f sec, %d hulls, %d vertices", + result, latency, numHulls, totalVertices); + break; + + default: + throw new IllegalStateException("runState = " + runState); + } + + return result; + } + // ************************************************************************* + // private methods + + /** + * Calculate the total number of vertices in the output shape. + */ + private void calculateTotalVertices() { + int sum = 0; + ChildCollisionShape[] children = shape.listChildren(); + for (ChildCollisionShape child : children) { + CollisionShape childShape = child.getShape(); + HullCollisionShape hull = (HullCollisionShape) childShape; + sum += hull.countHullVertices(); + } + this.totalVertices = sum; + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathMode.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathMode.java index 99c1a29a2..d0badaacc 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathMode.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathMode.java @@ -1,166 +1,166 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "filePath" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class FilePathMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(FilePathMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - /** - * action-string prefix to alter the filesystem path prefix - */ - final private static String apSetPathPrefix = "set pathPrefix "; - /** - * action string to browse the filesystem - */ - final private static String asBrowse = "browse"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized mode. - */ - FilePathMode() { - super("filePath"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.dumpRenderer, KeyInput.KEY_P); - bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - FilePathScreen screen - = VhacdTuner.findAppState(FilePathScreen.class); - assert screen.isEnabled(); - - if (asBrowse.equals(actionString)) { - screen.browse(); - handled = true; - - } else if (actionString.startsWith(apSetPathPrefix)) { - String pathPrefix - = MyString.remainder(actionString, apSetPathPrefix); - screen.setPathPrefix(pathPrefix); - handled = true; - - } else if (Action.nextScreen.equals(actionString)) { - nextScreen(); - handled = true; - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Advance to the LoadScreen if possible. - */ - private void nextScreen() { - FilePathScreen screen = VhacdTuner.findAppState(FilePathScreen.class); - assert screen.isEnabled(); - - String feedback = FilePathScreen.feedback(); - if (feedback.isEmpty()) { - setEnabled(false); - InputMode load = InputMode.findMode("load"); - load.setEnabled(true); - } - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "filePath" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class FilePathMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(FilePathMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + /** + * action-string prefix to alter the filesystem path prefix + */ + final private static String apSetPathPrefix = "set pathPrefix "; + /** + * action string to browse the filesystem + */ + final private static String asBrowse = "browse"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized mode. + */ + FilePathMode() { + super("filePath"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.dumpRenderer, KeyInput.KEY_P); + bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + FilePathScreen screen + = VhacdTuner.findAppState(FilePathScreen.class); + assert screen.isEnabled(); + + if (asBrowse.equals(actionString)) { + screen.browse(); + handled = true; + + } else if (actionString.startsWith(apSetPathPrefix)) { + String pathPrefix + = MyString.remainder(actionString, apSetPathPrefix); + screen.setPathPrefix(pathPrefix); + handled = true; + + } else if (Action.nextScreen.equals(actionString)) { + nextScreen(); + handled = true; + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Advance to the LoadScreen if possible. + */ + private void nextScreen() { + FilePathScreen screen = VhacdTuner.findAppState(FilePathScreen.class); + assert screen.isEnabled(); + + String feedback = FilePathScreen.feedback(); + if (feedback.isEmpty()) { + setEnabled(false); + InputMode load = InputMode.findMode("load"); + load.setEnabled(true); + } + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathScreen.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathScreen.java index 182f06e22..69c59200c 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathScreen.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/FilePathScreen.java @@ -1,326 +1,326 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.elements.Element; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InitialState; -import jme3utilities.MyString; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.nifty.PopupMenuBuilder; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "filePath" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class FilePathScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger - = Logger.getLogger(FilePathScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of GUI button to proceed to the next Screen - */ - private Element nextElement; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - FilePathScreen() { - super("filePath", "Interface/Nifty/screens/tuner/filePath.xml", - InitialState.Disabled); - setSubmenuWarp(0.5f, 0.5f); - } - // ************************************************************************* - // new methods exposed - - /** - * Handle a "browse" action to begin browsing the file system. - */ - void browse() { - Map fileMap = Heart.driveMap(); - - // Add the current working directory to the file map. - String workPath = System.getProperty("user.dir"); - File work = new File(workPath); - if (work.isDirectory()) { - String absolutePath = Heart.fixPath(workPath); - fileMap.put(absolutePath, work); - } - - // Add the user's home directory to the file map. - String homePath = System.getProperty("user.home"); - File home = new File(homePath); - if (home.isDirectory()) { - String absolutePath = Heart.fixPath(homePath); - fileMap.put(absolutePath, home); - } - /* - * If a filesystem path is selected, add its parent directory - * to the file map. - */ - Model model = VhacdTuner.getModel(); - String filePath = model.filePath(); - if (!filePath.isEmpty()) { - File file = new File(filePath); - File parent = file.getParentFile(); - String parentPath = parent.getPath(); - String absolutePath = Heart.fixPath(parentPath); - fileMap.put(absolutePath, parent); - } - - // Build and show a popup menu. - String actionPrefix = "set pathPrefix "; - PopupMenuBuilder builder = buildFileMenu(fileMap); - showPopupMenu(actionPrefix, builder); - } - - /** - * Determine user feedback (if any) regarding the "next screen" action. - * - * @return "" if ready to proceed, otherwise an explanatory message - */ - static String feedback() { - Model model = VhacdTuner.getModel(); - String filePath = model.filePath(); - - String result = ""; - if (!filePath.contains("/")) { - result = "No model is selected yet."; - } - - return result; - } - - /** - * Handle a "set pathPrefix" action. - * - * @param pathPrefix the user-selected filesystem-path prefix (not null, not - * empty) - */ - void setPathPrefix(String pathPrefix) { - assert pathPrefix != null; - - String absPathPrefix = Heart.fixPath(pathPrefix); - File file = new File(absPathPrefix); - - boolean isDirectory = file.isDirectory(); - if (!isDirectory && file.canRead()) { // complete path to readable file - Model model = VhacdTuner.getModel(); - model.setFilePath(absPathPrefix); - - } else { - Map fileMap; - String actionPrefix; - if (isDirectory) { - fileMap = directoryMap(absPathPrefix, ""); - actionPrefix = "set pathPrefix " + absPathPrefix; - - } else { // an incomplete path - File parent = file.getParentFile(); - String parentPath = parent.getPath(); - parentPath = Heart.fixPath(parentPath); - - String name = file.getName(); - fileMap = directoryMap(parentPath, name); - actionPrefix = "set pathPrefix " + parentPath; - } - if (!actionPrefix.endsWith("/")) { - actionPrefix += "/"; - } - - // Build and show a popup menu. - PopupMenuBuilder builder = buildFileMenu(fileMap); - showPopupMenu(actionPrefix, builder); - } - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("filePath"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - Model model = VhacdTuner.getModel(); - String filePath = model.filePath(); - setStatusText("filePath", " " + filePath); - - String feedback = feedback(); - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - } - // ************************************************************************* - // private methods - - /** - * Build a file-selection popup menu based on the specified file map. - * - * @param fileMap the map of files to include (not null) - * @return a new instance (not null) - */ - private PopupMenuBuilder buildFileMenu(Map fileMap) { - assert fileMap != null; - - // Generate a list of file names (and prefixes) to display in the menu. - Set nameSet = fileMap.keySet(); - assert !nameSet.contains("."); - List nameList = new ArrayList<>(nameSet); - - // Reduce the list as necessary to fit on the screen. - int height = cam.getHeight(); - int maxMenuItems = height / 26; - MyString.reduce(nameList, maxMenuItems); - - // Sort the list and build the menu. - Collections.sort(nameList); - PopupMenuBuilder result = new PopupMenuBuilder(); - for (String name : nameList) { - if (fileMap.containsKey(name)) { - File file = fileMap.get(name); - if (file.isDirectory()) { - result.add(name, "Textures/icons/folder.png"); - } else if (name.endsWith(".j3o")) { - result.add(name, "Textures/icons/jme.png"); - } else if (name.endsWith(".glb")) { - result.add(name, "Textures/icons/jme.png"); - } else if (name.endsWith(".gltf")) { - result.add(name, "Textures/icons/jme.png"); - } - } else { // prefix - result.add(name, "Textures/icons/ellipsis.png"); - } - } - - return result; - } - - /** - * Build a map of files, in the specified directory, whose names have the - * specified prefix. - * - * @param dirPath the filesystem path to the directory (not null) - * @param namePrefix required name prefix (not null) - * @return a new instance (not null) - */ - private static Map directoryMap( - String dirPath, String namePrefix) { - assert dirPath != null; - assert namePrefix != null; - - Map fileMap = new TreeMap<>(); - /* - * Initialize the map with subdirectories and readable files. - * Exclude names that start with ".". - */ - File directory = new File(dirPath); - File[] files = directory.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isDirectory() || file.canRead()) { - String name = file.getName(); - if (name.startsWith(namePrefix) && !name.startsWith(".")) { - fileMap.put(name, file); - } - } - } - } - - // Add ".." if a parent directory exists. - File parent = directory.getParentFile(); - if (parent != null) { - if ("..".startsWith(namePrefix)) { - fileMap.put("..", parent); - } - } - - return fileMap; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.elements.Element; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InitialState; +import jme3utilities.MyString; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.nifty.PopupMenuBuilder; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "filePath" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class FilePathScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger + = Logger.getLogger(FilePathScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of GUI button to proceed to the next Screen + */ + private Element nextElement; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + FilePathScreen() { + super("filePath", "Interface/Nifty/screens/tuner/filePath.xml", + InitialState.Disabled); + setSubmenuWarp(0.5f, 0.5f); + } + // ************************************************************************* + // new methods exposed + + /** + * Handle a "browse" action to begin browsing the file system. + */ + void browse() { + Map fileMap = Heart.driveMap(); + + // Add the current working directory to the file map. + String workPath = System.getProperty("user.dir"); + File work = new File(workPath); + if (work.isDirectory()) { + String absolutePath = Heart.fixPath(workPath); + fileMap.put(absolutePath, work); + } + + // Add the user's home directory to the file map. + String homePath = System.getProperty("user.home"); + File home = new File(homePath); + if (home.isDirectory()) { + String absolutePath = Heart.fixPath(homePath); + fileMap.put(absolutePath, home); + } + /* + * If a filesystem path is selected, add its parent directory + * to the file map. + */ + Model model = VhacdTuner.getModel(); + String filePath = model.filePath(); + if (!filePath.isEmpty()) { + File file = new File(filePath); + File parent = file.getParentFile(); + String parentPath = parent.getPath(); + String absolutePath = Heart.fixPath(parentPath); + fileMap.put(absolutePath, parent); + } + + // Build and show a popup menu. + String actionPrefix = "set pathPrefix "; + PopupMenuBuilder builder = buildFileMenu(fileMap); + showPopupMenu(actionPrefix, builder); + } + + /** + * Determine user feedback (if any) regarding the "next screen" action. + * + * @return "" if ready to proceed, otherwise an explanatory message + */ + static String feedback() { + Model model = VhacdTuner.getModel(); + String filePath = model.filePath(); + + String result = ""; + if (!filePath.contains("/")) { + result = "No model is selected yet."; + } + + return result; + } + + /** + * Handle a "set pathPrefix" action. + * + * @param pathPrefix the user-selected filesystem-path prefix (not null, not + * empty) + */ + void setPathPrefix(String pathPrefix) { + assert pathPrefix != null; + + String absPathPrefix = Heart.fixPath(pathPrefix); + File file = new File(absPathPrefix); + + boolean isDirectory = file.isDirectory(); + if (!isDirectory && file.canRead()) { // complete path to readable file + Model model = VhacdTuner.getModel(); + model.setFilePath(absPathPrefix); + + } else { + Map fileMap; + String actionPrefix; + if (isDirectory) { + fileMap = directoryMap(absPathPrefix, ""); + actionPrefix = "set pathPrefix " + absPathPrefix; + + } else { // an incomplete path + File parent = file.getParentFile(); + String parentPath = parent.getPath(); + parentPath = Heart.fixPath(parentPath); + + String name = file.getName(); + fileMap = directoryMap(parentPath, name); + actionPrefix = "set pathPrefix " + parentPath; + } + if (!actionPrefix.endsWith("/")) { + actionPrefix += "/"; + } + + // Build and show a popup menu. + PopupMenuBuilder builder = buildFileMenu(fileMap); + showPopupMenu(actionPrefix, builder); + } + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("filePath"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + Model model = VhacdTuner.getModel(); + String filePath = model.filePath(); + setStatusText("filePath", " " + filePath); + + String feedback = feedback(); + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + } + // ************************************************************************* + // private methods + + /** + * Build a file-selection popup menu based on the specified file map. + * + * @param fileMap the map of files to include (not null) + * @return a new instance (not null) + */ + private PopupMenuBuilder buildFileMenu(Map fileMap) { + assert fileMap != null; + + // Generate a list of file names (and prefixes) to display in the menu. + Set nameSet = fileMap.keySet(); + assert !nameSet.contains("."); + List nameList = new ArrayList<>(nameSet); + + // Reduce the list as necessary to fit on the screen. + int height = cam.getHeight(); + int maxMenuItems = height / 26; + MyString.reduce(nameList, maxMenuItems); + + // Sort the list and build the menu. + Collections.sort(nameList); + PopupMenuBuilder result = new PopupMenuBuilder(); + for (String name : nameList) { + if (fileMap.containsKey(name)) { + File file = fileMap.get(name); + if (file.isDirectory()) { + result.add(name, "Textures/icons/folder.png"); + } else if (name.endsWith(".j3o")) { + result.add(name, "Textures/icons/jme.png"); + } else if (name.endsWith(".glb")) { + result.add(name, "Textures/icons/jme.png"); + } else if (name.endsWith(".gltf")) { + result.add(name, "Textures/icons/jme.png"); + } + } else { // prefix + result.add(name, "Textures/icons/ellipsis.png"); + } + } + + return result; + } + + /** + * Build a map of files, in the specified directory, whose names have the + * specified prefix. + * + * @param dirPath the filesystem path to the directory (not null) + * @param namePrefix required name prefix (not null) + * @return a new instance (not null) + */ + private static Map directoryMap( + String dirPath, String namePrefix) { + assert dirPath != null; + assert namePrefix != null; + + Map fileMap = new TreeMap<>(); + /* + * Initialize the map with subdirectories and readable files. + * Exclude names that start with ".". + */ + File directory = new File(dirPath); + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory() || file.canRead()) { + String name = file.getName(); + if (name.startsWith(namePrefix) && !name.startsWith(".")) { + fileMap.put(name, file); + } + } + } + } + + // Add ".." if a parent directory exists. + File parent = directory.getParentFile(); + if (parent != null) { + if ("..".startsWith(namePrefix)) { + fileMap.put("..", parent); + } + } + + return fileMap; + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadMode.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadMode.java index cc172d43e..36191c98b 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadMode.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadMode.java @@ -1,199 +1,199 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "load" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class LoadMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(LoadMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - /** - * action strings: - */ - final private static String asLoad = "load"; - final private static String asMorePath = "more path"; - final private static String asMoreRoot = "more root"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized mode. - */ - LoadMode() { - super("load"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); - bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); - - bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); - bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); - bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); - bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); - bindSignal("orbitLeft", KeyInput.KEY_A); - bindSignal("orbitRight", KeyInput.KEY_D); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - - bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - Model model = VhacdTuner.getModel(); - LoadScreen screen = VhacdTuner.findAppState(LoadScreen.class); - assert screen.isEnabled(); - - handled = true; - switch (actionString) { - case asLoad: - model.load(); - break; - - case asMorePath: - model.morePath(); - break; - - case asMoreRoot: - model.moreRoot(); - break; - - case Action.nextScreen: - nextScreen(); - break; - - case Action.previousScreen: - model.unload(); - previousScreen(); - break; - - case Action.toggleAxes: - screen.toggleShowingAxes(); - break; - - default: - handled = false; - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Advance to the TestScreen if possible. - */ - private void nextScreen() { - String feedback = LoadScreen.feedback(); - if (feedback.isEmpty()) { - setEnabled(false); - InputMode test = InputMode.findMode("test"); - test.setEnabled(true); - } - } - - /** - * Go back to the FilePathScreen. - */ - private void previousScreen() { - setEnabled(false); - InputMode filePath = InputMode.findMode("filePath"); - filePath.setEnabled(true); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "load" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class LoadMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(LoadMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + /** + * action strings: + */ + final private static String asLoad = "load"; + final private static String asMorePath = "more path"; + final private static String asMoreRoot = "more root"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized mode. + */ + LoadMode() { + super("load"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); + bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); + + bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); + bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); + bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); + bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); + bindSignal("orbitLeft", KeyInput.KEY_A); + bindSignal("orbitRight", KeyInput.KEY_D); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + + bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + Model model = VhacdTuner.getModel(); + LoadScreen screen = VhacdTuner.findAppState(LoadScreen.class); + assert screen.isEnabled(); + + handled = true; + switch (actionString) { + case asLoad: + model.load(); + break; + + case asMorePath: + model.morePath(); + break; + + case asMoreRoot: + model.moreRoot(); + break; + + case Action.nextScreen: + nextScreen(); + break; + + case Action.previousScreen: + model.unload(); + previousScreen(); + break; + + case Action.toggleAxes: + screen.toggleShowingAxes(); + break; + + default: + handled = false; + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Advance to the TestScreen if possible. + */ + private void nextScreen() { + String feedback = LoadScreen.feedback(); + if (feedback.isEmpty()) { + setEnabled(false); + InputMode test = InputMode.findMode("test"); + test.setEnabled(true); + } + } + + /** + * Go back to the FilePathScreen. + */ + private void previousScreen() { + setEnabled(false); + InputMode filePath = InputMode.findMode("filePath"); + filePath.setEnabled(true); + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadScreen.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadScreen.java index c0908ed6c..07c7be3ad 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadScreen.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/LoadScreen.java @@ -1,250 +1,250 @@ -/* - Copyright (c) 2019-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.elements.Element; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InitialState; -import jme3utilities.MyCamera; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "load" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class LoadScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(LoadScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of GUI button to proceed to the next Screen - */ - private Element nextElement; - /** - * root spatial of the C-G model being previewed - */ - private Spatial viewedSpatial; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - LoadScreen() { - super("load", "Interface/Nifty/screens/tuner/load.xml", - InitialState.Disabled); - } - // ************************************************************************* - // new methods exposed - - /** - * Determine user feedback (if any) regarding the "next screen" action. - * - * @return "" if ready to proceed, otherwise an explanatory message - */ - static String feedback() { - Model model = VhacdTuner.getModel(); - Spatial nextSpatial = model.getRootSpatial(); - String loadException = model.loadExceptionString(); - - String result; - if (!loadException.isEmpty()) { - result = loadException; - } else if (nextSpatial == null) { - result = "The model hasn't been loaded yet."; - } else { - result = ""; - } - - return result; - } - - /** - * Toggle the visibility of the world axes. - */ - void toggleShowingAxes() { - Model model = VhacdTuner.getModel(); - model.toggleAxes(); - VhacdTuner.updateAxes(rootNode); - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("load"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - cam.setName("default camera"); - cam.setLocation(new Vector3f(0f, 0f, 10f)); - MyCamera.setNearFar(cam, 0.1f, 1000f); - cam.setRotation(new Quaternion(0f, 1f, 0f, 0f)); - - flyCam.setDragToRotate(true); - viewPort.setEnabled(true); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - - VhacdTuner tuner = VhacdTuner.getApplication(); - tuner.clearScene(); - this.viewedSpatial = null; - - VhacdTuner.updateAxes(rootNode); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - updateFeedback(); - updatePath(); - updateViewButtons(); - - Model model = VhacdTuner.getModel(); - Spatial nextSpatial = model.getRootSpatial(); - if (nextSpatial != this.viewedSpatial) { - VhacdTuner tuner = VhacdTuner.getApplication(); - tuner.clearScene(); - this.viewedSpatial = nextSpatial; - if (nextSpatial != null) { - Spatial cgModel = Heart.deepCopy(nextSpatial); - tuner.makeScene(cgModel); - } - } - } - // ************************************************************************* - // private methods - - /** - * Update the feedback line and the load/next buttons. - */ - private void updateFeedback() { - Model model = VhacdTuner.getModel(); - Spatial nextSpatial = model.getRootSpatial(); - String loadException = model.loadExceptionString(); - - String loadButton = ""; - if (loadException.isEmpty() && nextSpatial == null) { - loadButton = "Load and preview"; - } - setButtonText("load", loadButton); - - String feedback = feedback(); - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - } - - /** - * Update the path status and "+" buttons. - */ - private void updatePath() { - Model model = VhacdTuner.getModel(); - - String assetPath = model.assetPath(); - String assetRoot = model.assetRoot(); - String[] pathComponents = assetPath.split("/"); - String[] rootComponents = assetRoot.split("/"); - - String morePathButton = ""; - String moreRootButton = ""; - if (pathComponents.length > 2) { - moreRootButton = "+"; - } - if (rootComponents.length > 1) { - morePathButton = "+"; - } - - setButtonText("morePath", morePathButton); - setButtonText("moreRoot", moreRootButton); - - setStatusText("assetPath", " " + assetPath); - setStatusText("assetRoot", " " + assetRoot); - } - - /** - * Update the buttons that toggle view elements. - */ - private void updateViewButtons() { - Model model = VhacdTuner.getModel(); - String axesText = model.isShowingAxes() ? "Hide axes" : "Show axes"; - setButtonText("axes", axesText); - } -} +/* + Copyright (c) 2019-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.elements.Element; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InitialState; +import jme3utilities.MyCamera; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "load" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class LoadScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(LoadScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of GUI button to proceed to the next Screen + */ + private Element nextElement; + /** + * root spatial of the C-G model being previewed + */ + private Spatial viewedSpatial; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + LoadScreen() { + super("load", "Interface/Nifty/screens/tuner/load.xml", + InitialState.Disabled); + } + // ************************************************************************* + // new methods exposed + + /** + * Determine user feedback (if any) regarding the "next screen" action. + * + * @return "" if ready to proceed, otherwise an explanatory message + */ + static String feedback() { + Model model = VhacdTuner.getModel(); + Spatial nextSpatial = model.getRootSpatial(); + String loadException = model.loadExceptionString(); + + String result; + if (!loadException.isEmpty()) { + result = loadException; + } else if (nextSpatial == null) { + result = "The model hasn't been loaded yet."; + } else { + result = ""; + } + + return result; + } + + /** + * Toggle the visibility of the world axes. + */ + void toggleShowingAxes() { + Model model = VhacdTuner.getModel(); + model.toggleAxes(); + VhacdTuner.updateAxes(rootNode); + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("load"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + cam.setName("default camera"); + cam.setLocation(new Vector3f(0f, 0f, 10f)); + MyCamera.setNearFar(cam, 0.1f, 1000f); + cam.setRotation(new Quaternion(0f, 1f, 0f, 0f)); + + flyCam.setDragToRotate(true); + viewPort.setEnabled(true); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + + VhacdTuner tuner = VhacdTuner.getApplication(); + tuner.clearScene(); + this.viewedSpatial = null; + + VhacdTuner.updateAxes(rootNode); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + updateFeedback(); + updatePath(); + updateViewButtons(); + + Model model = VhacdTuner.getModel(); + Spatial nextSpatial = model.getRootSpatial(); + if (nextSpatial != this.viewedSpatial) { + VhacdTuner tuner = VhacdTuner.getApplication(); + tuner.clearScene(); + this.viewedSpatial = nextSpatial; + if (nextSpatial != null) { + Spatial cgModel = Heart.deepCopy(nextSpatial); + tuner.makeScene(cgModel); + } + } + } + // ************************************************************************* + // private methods + + /** + * Update the feedback line and the load/next buttons. + */ + private void updateFeedback() { + Model model = VhacdTuner.getModel(); + Spatial nextSpatial = model.getRootSpatial(); + String loadException = model.loadExceptionString(); + + String loadButton = ""; + if (loadException.isEmpty() && nextSpatial == null) { + loadButton = "Load and preview"; + } + setButtonText("load", loadButton); + + String feedback = feedback(); + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + } + + /** + * Update the path status and "+" buttons. + */ + private void updatePath() { + Model model = VhacdTuner.getModel(); + + String assetPath = model.assetPath(); + String assetRoot = model.assetRoot(); + String[] pathComponents = assetPath.split("/"); + String[] rootComponents = assetRoot.split("/"); + + String morePathButton = ""; + String moreRootButton = ""; + if (pathComponents.length > 2) { + moreRootButton = "+"; + } + if (rootComponents.length > 1) { + morePathButton = "+"; + } + + setButtonText("morePath", morePathButton); + setButtonText("moreRoot", moreRootButton); + + setStatusText("assetPath", " " + assetPath); + setStatusText("assetRoot", " " + assetRoot); + } + + /** + * Update the buttons that toggle view elements. + */ + private void updateViewButtons() { + Model model = VhacdTuner.getModel(); + String axesText = model.isShowingAxes() ? "Hide axes" : "Show axes"; + setButtonText("axes", axesText); + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Model.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Model.java index e2b84774f..67688e78f 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Model.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/Model.java @@ -1,849 +1,849 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.asset.AssetManager; -import com.jme3.scene.Spatial; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; -import jme3utilities.MyMesh; -import jme3utilities.math.VectorSet; -import jme3utilities.ui.Locators; -import vhacd.ACDMode; -import vhacd.VHACD; -import vhacd.VHACDParameters; -import vhacd4.Vhacd4; -import vhacd4.Vhacd4Parameters; - -/** - * The state information (MVC model) in the VhacdTuner application. - * - * @author Stephen Gold sgold@sonic.net - */ -class Model { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger - = Logger.getLogger(Model.class.getName()); - // ************************************************************************* - // fields - - /** - * whether to display axes in the TestScreen - */ - private boolean isShowingAxes = false; - /** - * parameters and results for the left side - */ - private DecompositionTest leftTest; - /** - * parameters and results for the right side - */ - private DecompositionTest rightTest; - /** - * parameters and results for the test currently running, or null if none - */ - private DecompositionTest runningTest; - /** - * parameters and results for the test being ranked, or null if none - */ - private DecompositionTest testBeingRanked; - /** - * Exception that occurred during load - */ - private Exception loadException; - /** - * distance of the furthest vertex from the model origin, or 2.5 if no - * vertices - */ - private float radius = 2.5f; - /** - * maximum rank of the test being ranked (0 → best, 1 → - * second-best, and so on), or -1 if no test being ranked - */ - private int maxRank = -1; - /** - * minimum rank of the test being ranked (0 → best, 1 → - * second-best, and so on), or -1 if no test being ranked - */ - private int minRank = -1; - /** - * number of components in the filesystem path to the asset root - */ - private int numComponentsInRoot; - /** - * ranked test results, ordered from best to worst - */ - final private List rankedTests = new LinkedList<>(); - /** - * map classic parameters to test results - */ - final private Map classicMap - = new HashMap<>(16); - /** - * map v4 parameters to test results - */ - final private Map v4Map - = new HashMap<>(16); - /** - * root spatial of the loaded C-G model - */ - private Spatial rootSpatial; - /** - * components of the filesystem path to the C-G model (not null) - */ - private String[] filePathComponents = new String[0]; - // ************************************************************************* - // new methods exposed - - /** - * Determine the asset path to the J3O/glTF asset. The filesystem path must - * be set. - * - * @return the path (not null, not empty) - */ - String assetPath() { - int numComponents = filePathComponents.length; - if (numComponents == 0) { - throw new RuntimeException("Filesystem path not set."); - } - assert numComponentsInRoot < numComponents : numComponents; - String[] resultComponents = Arrays.copyOfRange( - filePathComponents, numComponentsInRoot, numComponents); - String result = String.join("/", resultComponents); - result = "/" + result; - - return result; - } - - /** - * Determine the filesystem path to the asset root. The filesystem path must - * be set. - * - * @return the path (not null, not empty) - */ - String assetRoot() { - int numComponents = filePathComponents.length; - if (numComponents == 0) { - throw new RuntimeException("Filesystem path not set."); - } - assert numComponentsInRoot < numComponents : numComponents; - String[] resultComponents = Arrays.copyOfRange( - filePathComponents, 0, numComponentsInRoot); - String result = String.join("/", resultComponents); - result += "/"; - - assert result != null; - assert !result.isEmpty(); - return result; - } - - /** - * Count how many tests have been ranked. - * - * @return the count (≥0) - */ - int countRankedTests() { - int result = rankedTests.size(); - assert result >= 0 : result; - return result; - } - - /** - * Return the filesystem path to the J3O/glTF file. - * - * @return the path (not null, may be empty) - */ - String filePath() { - String result = String.join("/", filePathComponents); - assert result != null; - return result; - } - - /** - * Return the rank of the specified test result. - * - * @param test the test result to rank (not null, unaffected) - * @return 0 → best, 1 → second-best, and so on, or -1 if test is - * unranked - */ - int findRank(DecompositionTest test) { - int result = rankedTests.indexOf(test); - return result; - } - - /** - * Return the test result with the specified rank. - * - * @param rank 0 → best, 1 → second-best, and so on - * @return the pre-existing instance, or null if no such test - */ - DecompositionTest findRankedTest(int rank) { - DecompositionTest result = null; - if (rankedTests.size() > rank) { - result = rankedTests.get(rank); - } - - return result; - } - - /** - * Access the parameters and results for the left side. - * - * @return the pre-existing instance, or null if none - */ - DecompositionTest getLeftTest() { - return leftTest; - } - - /** - * Access the parameters and results for the right side. - * - * @return the pre-existing instance, or null if none - */ - DecompositionTest getRightTest() { - return rightTest; - } - - /** - * Access the root spatial of the loaded C-G model. - * - * @return the pre-existing Spatial, or null if no model loaded - */ - Spatial getRootSpatial() { - return rootSpatial; - } - - /** - * Test whether the specified test has been ranked. - * - * @param test (unaffected) - * @return true if ranked, otherwise false - */ - boolean isRanked(DecompositionTest test) { - if (rankedTests.contains(test)) { - return true; - } else { - return false; - } - } - - /** - * Test whether a test is currently being ranked. - * - * @return true if being ranked, otherwise false - */ - boolean isRanking() { - if (testBeingRanked == null) { - return false; - } else { - return true; - } - } - - /** - * Test whether the world axes will be rendered. - * - * @return true if rendered, otherwise false - */ - boolean isShowingAxes() { - return isShowingAxes; - } - - /** - * Attempt to load a C-G model. The filesystem path must have been - * previously set. If successful, {@code rootSpatial} is initialized. - * Otherwise, {@code rootSpatial == null} and loadException is set. - */ - void load() { - int numComponents = filePathComponents.length; - if (numComponents == 0) { - throw new RuntimeException("Filesystem path not set."); - } - - unload(); - String assetRoot = assetRoot(); - String assetPath = assetPath(); - - Locators.save(); - Locators.unregisterAll(); - Locators.registerFilesystem(assetRoot); - Locators.registerDefault(); - AssetManager assetManager = Locators.getAssetManager(); - assetManager.clearCache(); - try { - this.rootSpatial = assetManager.loadModel(assetPath); - this.loadException = null; - } catch (RuntimeException exception) { - this.rootSpatial = null; - this.loadException = exception; - } - Locators.restore(); - - if (rootSpatial != null) { - VectorSet set = MyMesh.listVertexLocations(rootSpatial, null); - if (set.numVectors() == 0) { - this.radius = 1f; - } else { - this.radius = set.maxLength(); - } - } else { - this.radius = 1f; - } - - // Invalidate all tests of the prior C-G model, if any. - rankedTests.clear(); - classicMap.clear(); - v4Map.clear(); - - Vhacd4Parameters v4 = new Vhacd4Parameters(); - DecompositionTest left = new DecompositionTest(v4); - setLeftTest(left); - - VHACDParameters classic = new VHACDParameters(); - DecompositionTest right = new DecompositionTest(classic); - setRightTest(right); - } - - /** - * Read the exception that occurred during the most recent load attempt. - * - * @return the exception message, or "" if none - */ - String loadExceptionString() { - String result = ""; - if (loadException != null) { - result = loadException.toString(); - } - - return result; - } - - /** - * Shift one component of the filesystem path from the asset root to the - * asset path. - */ - void morePath() { - unload(); - --numComponentsInRoot; - } - - /** - * Shift one component of the filesystem path from the asset path to the - * asset root. - */ - void moreRoot() { - unload(); - ++numComponentsInRoot; - } - - /** - * Advance the fill mode of the left-side test to the next value. - */ - void nextFillModeLeft() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = leftTest.copyV4(); - copy.nextFillMode(); - this.leftTest = getTest(copy); - } - - /** - * Advance the fill mode of the right-side test to the next value. - */ - void nextFillModeRight() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = rightTest.copyV4(); - copy.nextFillMode(); - this.rightTest = getTest(copy); - } - - /** - * Check for completion of a running test. - */ - void pollForTaskCompletion() { - if (runningTest == null || !runningTest.hasBeenRun()) { - return; - } - assert !isRanked(runningTest); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - screen.closeAllPopups(); - - if (rankedTests.isEmpty()) { - /* - * There are no ranked tests to compare with, - * so the test that just was run is #1 for now. - */ - rankedTests.add(runningTest); - - } else { // Begin ranking the test that was just run. - this.minRank = 0; - this.maxRank = rankedTests.size(); - int compareIndex = (minRank + maxRank) / 2; - DecompositionTest compareTest = rankedTests.get(compareIndex); - this.testBeingRanked = runningTest; - - if (testBeingRanked == rightTest) { - assert isRanked(leftTest) || !leftTest.hasBeenRun(); - this.leftTest = compareTest; - } else { - assert testBeingRanked == leftTest; - assert isRanked(rightTest) || !rightTest.hasBeenRun(); - this.rightTest = compareTest; - } - } - - this.runningTest = null; - } - - /** - * Record a preference for the first argument over the second argument. - * - * @param better a test preferred over {@code worse} (not null, unaffected) - * @param worse a test to be ranked lower than {@code better} (not null, - * unaffected) - */ - void prefer(DecompositionTest better, DecompositionTest worse) { - assert isRanking(); - assert maxRank >= 0 : maxRank; - assert minRank >= 0 : minRank; - assert minRank < maxRank; - - if (testBeingRanked == better) { - int compareIndex = rankedTests.indexOf(worse); - assert compareIndex >= 0 : compareIndex; - this.maxRank = compareIndex; - - } else { - assert testBeingRanked == worse; - int compareIndex = rankedTests.indexOf(better); - assert compareIndex >= 0 : compareIndex; - this.minRank = compareIndex + 1; - } - - if (minRank == maxRank) { // The test's rank has been determined. - rankedTests.add(minRank, testBeingRanked); - this.maxRank = -1; - this.minRank = -1; - this.testBeingRanked = null; - - } else { - int compareIndex = (minRank + maxRank) / 2; - DecompositionTest compareTest = rankedTests.get(compareIndex); - if (testBeingRanked == rightTest) { - assert isRanked(leftTest); - this.leftTest = compareTest; - } else { - assert testBeingRanked == leftTest; - assert isRanked(rightTest); - this.rightTest = compareTest; - } - } - } - - /** - * Return the overall size of the model. - * - * @return the distance of the furthest vertex from the model origin, or 2.5 - * if no vertices - */ - float radius() { - return radius; - } - - /** - * Alter the model's filesystem path. - * - * @param path the desired filesystem path (not null, contains a "/") - */ - void setFilePath(String path) { - assert path != null; - assert path.contains("/"); - - this.filePathComponents = path.split("/"); - this.numComponentsInRoot = 1; - this.loadException = null; - unload(); - /* - * Use heuristics to guess how many components there are - * in the filesystem path to the asset root. - */ - int numComponents = filePathComponents.length; - assert numComponents > 0 : numComponents; - for (int componentI = 0; componentI < numComponents; ++componentI) { - String component = filePathComponents[componentI]; - switch (component) { - case "assets": - case "resources": - case "Written Assets": - if (componentI > 1) { - numComponentsInRoot = componentI - 1; - } - break; - case "Models": - if (componentI > 0 && componentI < numComponents) { - numComponentsInRoot = componentI; - } - break; - default: - } - } - } - - /** - * Select the test for the left side. - * - * @param test a test with the desired parameters (not null) - */ - void setLeftTest(DecompositionTest test) { - if (test.isClassic()) { - VHACDParameters parameters = test.copyClassic(); - this.leftTest = getTest(parameters); - } else { - Vhacd4Parameters parameters = test.copyV4(); - this.leftTest = getTest(parameters); - } - } - - /** - * Select the test for the right side. - * - * @param test a test with the desired parameters (not null) - */ - void setRightTest(DecompositionTest test) { - if (test.isClassic()) { - VHACDParameters parameters = test.copyClassic(); - this.rightTest = getTest(parameters); - } else { - Vhacd4Parameters parameters = test.copyV4(); - this.rightTest = getTest(parameters); - } - } - - /** - * Start a thread to run the specified test. - * - * @param test the test to run (not null, not running) - */ - void startTest(DecompositionTest test) { - assert !test.hasBeenRun(); - assert !test.isRunning(); - assert runningTest == null; - - ProgressDialog progressListener = new ProgressDialog(); - Vhacd4.addProgressListener(progressListener); - VHACD.addProgressListener(progressListener); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - screen.closeAllPopups(); - screen.showConfirmDialog("", "", "", progressListener); - - this.runningTest = test; - Thread runThread = new Thread(test); - runThread.start(); - } - - /** - * Abort the current ranking sequence, if any. - */ - void stopRanking() { - this.maxRank = -1; - this.minRank = -1; - this.testBeingRanked = null; - } - - /** - * Toggle the ACD mode of the left-side test. - */ - void toggleAcdModeLeft() { - if (isRanking()) { - return; - } - VHACDParameters copy = leftTest.copyClassic(); - ACDMode oldMode = copy.getACDMode(); - switch (oldMode) { - case TETRAHEDRON: - copy.setACDMode(ACDMode.VOXEL); - break; - case VOXEL: - copy.setACDMode(ACDMode.TETRAHEDRON); - break; - default: - throw new IllegalStateException("oldMode = " + oldMode); - } - this.leftTest = getTest(copy); - } - - /** - * Toggle the ACD mode of the right-side test. - */ - void toggleAcdModeRight() { - if (isRanking()) { - return; - } - VHACDParameters copy = rightTest.copyClassic(); - ACDMode oldMode = copy.getACDMode(); - switch (oldMode) { - case TETRAHEDRON: - copy.setACDMode(ACDMode.VOXEL); - break; - case VOXEL: - copy.setACDMode(ACDMode.TETRAHEDRON); - break; - default: - throw new IllegalStateException("oldMode = " + oldMode); - } - this.rightTest = getTest(copy); - } - - /** - * Toggle the async flag of the left-side test. - */ - void toggleAsyncLeft() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = leftTest.copyV4(); - boolean oldSetting = copy.isAsync(); - copy.setAsync(!oldSetting); - this.leftTest = getTest(copy); - } - - /** - * Toggle the async flag of the right-side test. - */ - void toggleAsyncRight() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = rightTest.copyV4(); - boolean oldSetting = copy.isAsync(); - copy.setAsync(!oldSetting); - this.rightTest = getTest(copy); - } - - /** - * Toggle the visibility of world axes in LoadScreen and TestScreen. - */ - void toggleAxes() { - this.isShowingAxes = !isShowingAxes; - } - - /** - * Toggle the "find best plane" option of the left-side test. - */ - void toggleFindBestPlaneLeft() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = leftTest.copyV4(); - boolean oldSetting = copy.isFindBestPlane(); - copy.setFindBestPlane(!oldSetting); - this.leftTest = getTest(copy); - } - - /** - * Toggle the "find best plane" option of the right-side test. - */ - void toggleFindBestPlaneRight() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = rightTest.copyV4(); - boolean oldSetting = copy.isFindBestPlane(); - copy.setFindBestPlane(!oldSetting); - this.rightTest = getTest(copy); - } - - /** - * Toggle the PCA setting of the left-side test. - */ - void togglePcaLeft() { - if (isRanking()) { - return; - } - VHACDParameters copy = leftTest.copyClassic(); - boolean oldSetting = copy.getPCA(); - copy.setPCA(!oldSetting); - this.leftTest = getTest(copy); - } - - /** - * Toggle the PCA setting of the right-side test. - */ - void togglePcaRight() { - if (isRanking()) { - return; - } - VHACDParameters copy = rightTest.copyClassic(); - boolean oldSetting = copy.getPCA(); - copy.setPCA(!oldSetting); - this.rightTest = getTest(copy); - } - - /** - * Toggle the shrink-wrap setting of the left-side test. - */ - void toggleShrinkLeft() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = leftTest.copyV4(); - boolean oldSetting = copy.isShrinkWrap(); - copy.setShrinkWrap(!oldSetting); - this.leftTest = getTest(copy); - } - - /** - * Toggle the shrink-wrap setting of the right-side test. - */ - void toggleShrinkRight() { - if (isRanking()) { - return; - } - Vhacd4Parameters copy = rightTest.copyV4(); - boolean oldSetting = copy.isShrinkWrap(); - copy.setShrinkWrap(!oldSetting); - this.rightTest = getTest(copy); - } - - /** - * Toggle the V-HACD version of the left-side test. - */ - void toggleVersionLeft() { - if (isRanking()) { - return; - } - this.leftTest = getToggledVersion(leftTest); - } - - /** - * Toggle the V-HACD version of the right-side test. - */ - void toggleVersionRight() { - if (isRanking()) { - return; - } - this.rightTest = getToggledVersion(rightTest); - } - - /** - * Unload the loaded C-G model, if any. - */ - void unload() { - this.rootSpatial = null; - } - // ************************************************************************* - // private methods - - /** - * Access the test results for the specified classic parameters. - * - * @param parameters the parameters (not null) - * @return a new or pre-existing test with the specified parameters - */ - DecompositionTest getTest(VHACDParameters parameters) { - DecompositionTest result = classicMap.get(parameters); - if (result == null) { - // Create a new test result. - result = new DecompositionTest(parameters); - classicMap.put(parameters, result); - } - - return result; - } - - /** - * Access the test results for the specified v4 parameters. - * - * @param parameters the parameters (not null) - * @return a new or pre-existing test with the specified parameters - */ - DecompositionTest getTest(Vhacd4Parameters parameters) { - DecompositionTest result = v4Map.get(parameters); - if (result == null) { - // Create a new test result. - result = new DecompositionTest(parameters); - v4Map.put(parameters, result); - } - - return result; - } - // ************************************************************************* - // private methods - - /** - * Construct a test with the opposite version from the specified test. - * - * @param test a test to use as a basis (not null, unaffected) - * @return a new or pre-existing test - */ - private DecompositionTest getToggledVersion(DecompositionTest test) { - DecompositionTest result; - - if (test.isClassic()) { - VHACDParameters classic = test.copyClassic(); - boolean debug = classic.getDebugEnabled(); - int maxVph = classic.getMaxVerticesPerHull(); - int resolution = classic.getVoxelResolution(); - - Vhacd4Parameters v4 = new Vhacd4Parameters(); - v4.setDebugEnabled(debug); - v4.setMaxVerticesPerHull(maxVph); - v4.setVoxelResolution(resolution); - - result = getTest(v4); - - } else { - Vhacd4Parameters v4 = test.copyV4(); - boolean debug = v4.getDebugEnabled(); - int maxVph = v4.getMaxVerticesPerHull(); - int resolution = v4.getVoxelResolution(); - - VHACDParameters classic = new VHACDParameters(); - classic.setDebugEnabled(debug); - classic.setMaxVerticesPerHull(maxVph); - classic.setVoxelResolution(resolution); - - result = getTest(classic); - } - - return result; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.Spatial; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import jme3utilities.MyMesh; +import jme3utilities.math.VectorSet; +import jme3utilities.ui.Locators; +import vhacd.ACDMode; +import vhacd.VHACD; +import vhacd.VHACDParameters; +import vhacd4.Vhacd4; +import vhacd4.Vhacd4Parameters; + +/** + * The state information (MVC model) in the VhacdTuner application. + * + * @author Stephen Gold sgold@sonic.net + */ +class Model { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger + = Logger.getLogger(Model.class.getName()); + // ************************************************************************* + // fields + + /** + * whether to display axes in the TestScreen + */ + private boolean isShowingAxes = false; + /** + * parameters and results for the left side + */ + private DecompositionTest leftTest; + /** + * parameters and results for the right side + */ + private DecompositionTest rightTest; + /** + * parameters and results for the test currently running, or null if none + */ + private DecompositionTest runningTest; + /** + * parameters and results for the test being ranked, or null if none + */ + private DecompositionTest testBeingRanked; + /** + * Exception that occurred during load + */ + private Exception loadException; + /** + * distance of the furthest vertex from the model origin, or 2.5 if no + * vertices + */ + private float radius = 2.5f; + /** + * maximum rank of the test being ranked (0 → best, 1 → + * second-best, and so on), or -1 if no test being ranked + */ + private int maxRank = -1; + /** + * minimum rank of the test being ranked (0 → best, 1 → + * second-best, and so on), or -1 if no test being ranked + */ + private int minRank = -1; + /** + * number of components in the filesystem path to the asset root + */ + private int numComponentsInRoot; + /** + * ranked test results, ordered from best to worst + */ + final private List rankedTests = new LinkedList<>(); + /** + * map classic parameters to test results + */ + final private Map classicMap + = new HashMap<>(16); + /** + * map v4 parameters to test results + */ + final private Map v4Map + = new HashMap<>(16); + /** + * root spatial of the loaded C-G model + */ + private Spatial rootSpatial; + /** + * components of the filesystem path to the C-G model (not null) + */ + private String[] filePathComponents = new String[0]; + // ************************************************************************* + // new methods exposed + + /** + * Determine the asset path to the J3O/glTF asset. The filesystem path must + * be set. + * + * @return the path (not null, not empty) + */ + String assetPath() { + int numComponents = filePathComponents.length; + if (numComponents == 0) { + throw new RuntimeException("Filesystem path not set."); + } + assert numComponentsInRoot < numComponents : numComponents; + String[] resultComponents = Arrays.copyOfRange( + filePathComponents, numComponentsInRoot, numComponents); + String result = String.join("/", resultComponents); + result = "/" + result; + + return result; + } + + /** + * Determine the filesystem path to the asset root. The filesystem path must + * be set. + * + * @return the path (not null, not empty) + */ + String assetRoot() { + int numComponents = filePathComponents.length; + if (numComponents == 0) { + throw new RuntimeException("Filesystem path not set."); + } + assert numComponentsInRoot < numComponents : numComponents; + String[] resultComponents = Arrays.copyOfRange( + filePathComponents, 0, numComponentsInRoot); + String result = String.join("/", resultComponents); + result += "/"; + + assert result != null; + assert !result.isEmpty(); + return result; + } + + /** + * Count how many tests have been ranked. + * + * @return the count (≥0) + */ + int countRankedTests() { + int result = rankedTests.size(); + assert result >= 0 : result; + return result; + } + + /** + * Return the filesystem path to the J3O/glTF file. + * + * @return the path (not null, may be empty) + */ + String filePath() { + String result = String.join("/", filePathComponents); + assert result != null; + return result; + } + + /** + * Return the rank of the specified test result. + * + * @param test the test result to rank (not null, unaffected) + * @return 0 → best, 1 → second-best, and so on, or -1 if test is + * unranked + */ + int findRank(DecompositionTest test) { + int result = rankedTests.indexOf(test); + return result; + } + + /** + * Return the test result with the specified rank. + * + * @param rank 0 → best, 1 → second-best, and so on + * @return the pre-existing instance, or null if no such test + */ + DecompositionTest findRankedTest(int rank) { + DecompositionTest result = null; + if (rankedTests.size() > rank) { + result = rankedTests.get(rank); + } + + return result; + } + + /** + * Access the parameters and results for the left side. + * + * @return the pre-existing instance, or null if none + */ + DecompositionTest getLeftTest() { + return leftTest; + } + + /** + * Access the parameters and results for the right side. + * + * @return the pre-existing instance, or null if none + */ + DecompositionTest getRightTest() { + return rightTest; + } + + /** + * Access the root spatial of the loaded C-G model. + * + * @return the pre-existing Spatial, or null if no model loaded + */ + Spatial getRootSpatial() { + return rootSpatial; + } + + /** + * Test whether the specified test has been ranked. + * + * @param test (unaffected) + * @return true if ranked, otherwise false + */ + boolean isRanked(DecompositionTest test) { + if (rankedTests.contains(test)) { + return true; + } else { + return false; + } + } + + /** + * Test whether a test is currently being ranked. + * + * @return true if being ranked, otherwise false + */ + boolean isRanking() { + if (testBeingRanked == null) { + return false; + } else { + return true; + } + } + + /** + * Test whether the world axes will be rendered. + * + * @return true if rendered, otherwise false + */ + boolean isShowingAxes() { + return isShowingAxes; + } + + /** + * Attempt to load a C-G model. The filesystem path must have been + * previously set. If successful, {@code rootSpatial} is initialized. + * Otherwise, {@code rootSpatial == null} and loadException is set. + */ + void load() { + int numComponents = filePathComponents.length; + if (numComponents == 0) { + throw new RuntimeException("Filesystem path not set."); + } + + unload(); + String assetRoot = assetRoot(); + String assetPath = assetPath(); + + Locators.save(); + Locators.unregisterAll(); + Locators.registerFilesystem(assetRoot); + Locators.registerDefault(); + AssetManager assetManager = Locators.getAssetManager(); + assetManager.clearCache(); + try { + this.rootSpatial = assetManager.loadModel(assetPath); + this.loadException = null; + } catch (RuntimeException exception) { + this.rootSpatial = null; + this.loadException = exception; + } + Locators.restore(); + + if (rootSpatial != null) { + VectorSet set = MyMesh.listVertexLocations(rootSpatial, null); + if (set.numVectors() == 0) { + this.radius = 1f; + } else { + this.radius = set.maxLength(); + } + } else { + this.radius = 1f; + } + + // Invalidate all tests of the prior C-G model, if any. + rankedTests.clear(); + classicMap.clear(); + v4Map.clear(); + + Vhacd4Parameters v4 = new Vhacd4Parameters(); + DecompositionTest left = new DecompositionTest(v4); + setLeftTest(left); + + VHACDParameters classic = new VHACDParameters(); + DecompositionTest right = new DecompositionTest(classic); + setRightTest(right); + } + + /** + * Read the exception that occurred during the most recent load attempt. + * + * @return the exception message, or "" if none + */ + String loadExceptionString() { + String result = ""; + if (loadException != null) { + result = loadException.toString(); + } + + return result; + } + + /** + * Shift one component of the filesystem path from the asset root to the + * asset path. + */ + void morePath() { + unload(); + --numComponentsInRoot; + } + + /** + * Shift one component of the filesystem path from the asset path to the + * asset root. + */ + void moreRoot() { + unload(); + ++numComponentsInRoot; + } + + /** + * Advance the fill mode of the left-side test to the next value. + */ + void nextFillModeLeft() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = leftTest.copyV4(); + copy.nextFillMode(); + this.leftTest = getTest(copy); + } + + /** + * Advance the fill mode of the right-side test to the next value. + */ + void nextFillModeRight() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = rightTest.copyV4(); + copy.nextFillMode(); + this.rightTest = getTest(copy); + } + + /** + * Check for completion of a running test. + */ + void pollForTaskCompletion() { + if (runningTest == null || !runningTest.hasBeenRun()) { + return; + } + assert !isRanked(runningTest); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + screen.closeAllPopups(); + + if (rankedTests.isEmpty()) { + /* + * There are no ranked tests to compare with, + * so the test that just was run is #1 for now. + */ + rankedTests.add(runningTest); + + } else { // Begin ranking the test that was just run. + this.minRank = 0; + this.maxRank = rankedTests.size(); + int compareIndex = (minRank + maxRank) / 2; + DecompositionTest compareTest = rankedTests.get(compareIndex); + this.testBeingRanked = runningTest; + + if (testBeingRanked == rightTest) { + assert isRanked(leftTest) || !leftTest.hasBeenRun(); + this.leftTest = compareTest; + } else { + assert testBeingRanked == leftTest; + assert isRanked(rightTest) || !rightTest.hasBeenRun(); + this.rightTest = compareTest; + } + } + + this.runningTest = null; + } + + /** + * Record a preference for the first argument over the second argument. + * + * @param better a test preferred over {@code worse} (not null, unaffected) + * @param worse a test to be ranked lower than {@code better} (not null, + * unaffected) + */ + void prefer(DecompositionTest better, DecompositionTest worse) { + assert isRanking(); + assert maxRank >= 0 : maxRank; + assert minRank >= 0 : minRank; + assert minRank < maxRank; + + if (testBeingRanked == better) { + int compareIndex = rankedTests.indexOf(worse); + assert compareIndex >= 0 : compareIndex; + this.maxRank = compareIndex; + + } else { + assert testBeingRanked == worse; + int compareIndex = rankedTests.indexOf(better); + assert compareIndex >= 0 : compareIndex; + this.minRank = compareIndex + 1; + } + + if (minRank == maxRank) { // The test's rank has been determined. + rankedTests.add(minRank, testBeingRanked); + this.maxRank = -1; + this.minRank = -1; + this.testBeingRanked = null; + + } else { + int compareIndex = (minRank + maxRank) / 2; + DecompositionTest compareTest = rankedTests.get(compareIndex); + if (testBeingRanked == rightTest) { + assert isRanked(leftTest); + this.leftTest = compareTest; + } else { + assert testBeingRanked == leftTest; + assert isRanked(rightTest); + this.rightTest = compareTest; + } + } + } + + /** + * Return the overall size of the model. + * + * @return the distance of the furthest vertex from the model origin, or 2.5 + * if no vertices + */ + float radius() { + return radius; + } + + /** + * Alter the model's filesystem path. + * + * @param path the desired filesystem path (not null, contains a "/") + */ + void setFilePath(String path) { + assert path != null; + assert path.contains("/"); + + this.filePathComponents = path.split("/"); + this.numComponentsInRoot = 1; + this.loadException = null; + unload(); + /* + * Use heuristics to guess how many components there are + * in the filesystem path to the asset root. + */ + int numComponents = filePathComponents.length; + assert numComponents > 0 : numComponents; + for (int componentI = 0; componentI < numComponents; ++componentI) { + String component = filePathComponents[componentI]; + switch (component) { + case "assets": + case "resources": + case "Written Assets": + if (componentI > 1) { + numComponentsInRoot = componentI - 1; + } + break; + case "Models": + if (componentI > 0 && componentI < numComponents) { + numComponentsInRoot = componentI; + } + break; + default: + } + } + } + + /** + * Select the test for the left side. + * + * @param test a test with the desired parameters (not null) + */ + void setLeftTest(DecompositionTest test) { + if (test.isClassic()) { + VHACDParameters parameters = test.copyClassic(); + this.leftTest = getTest(parameters); + } else { + Vhacd4Parameters parameters = test.copyV4(); + this.leftTest = getTest(parameters); + } + } + + /** + * Select the test for the right side. + * + * @param test a test with the desired parameters (not null) + */ + void setRightTest(DecompositionTest test) { + if (test.isClassic()) { + VHACDParameters parameters = test.copyClassic(); + this.rightTest = getTest(parameters); + } else { + Vhacd4Parameters parameters = test.copyV4(); + this.rightTest = getTest(parameters); + } + } + + /** + * Start a thread to run the specified test. + * + * @param test the test to run (not null, not running) + */ + void startTest(DecompositionTest test) { + assert !test.hasBeenRun(); + assert !test.isRunning(); + assert runningTest == null; + + ProgressDialog progressListener = new ProgressDialog(); + Vhacd4.addProgressListener(progressListener); + VHACD.addProgressListener(progressListener); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + screen.closeAllPopups(); + screen.showConfirmDialog("", "", "", progressListener); + + this.runningTest = test; + Thread runThread = new Thread(test); + runThread.start(); + } + + /** + * Abort the current ranking sequence, if any. + */ + void stopRanking() { + this.maxRank = -1; + this.minRank = -1; + this.testBeingRanked = null; + } + + /** + * Toggle the ACD mode of the left-side test. + */ + void toggleAcdModeLeft() { + if (isRanking()) { + return; + } + VHACDParameters copy = leftTest.copyClassic(); + ACDMode oldMode = copy.getACDMode(); + switch (oldMode) { + case TETRAHEDRON: + copy.setACDMode(ACDMode.VOXEL); + break; + case VOXEL: + copy.setACDMode(ACDMode.TETRAHEDRON); + break; + default: + throw new IllegalStateException("oldMode = " + oldMode); + } + this.leftTest = getTest(copy); + } + + /** + * Toggle the ACD mode of the right-side test. + */ + void toggleAcdModeRight() { + if (isRanking()) { + return; + } + VHACDParameters copy = rightTest.copyClassic(); + ACDMode oldMode = copy.getACDMode(); + switch (oldMode) { + case TETRAHEDRON: + copy.setACDMode(ACDMode.VOXEL); + break; + case VOXEL: + copy.setACDMode(ACDMode.TETRAHEDRON); + break; + default: + throw new IllegalStateException("oldMode = " + oldMode); + } + this.rightTest = getTest(copy); + } + + /** + * Toggle the async flag of the left-side test. + */ + void toggleAsyncLeft() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = leftTest.copyV4(); + boolean oldSetting = copy.isAsync(); + copy.setAsync(!oldSetting); + this.leftTest = getTest(copy); + } + + /** + * Toggle the async flag of the right-side test. + */ + void toggleAsyncRight() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = rightTest.copyV4(); + boolean oldSetting = copy.isAsync(); + copy.setAsync(!oldSetting); + this.rightTest = getTest(copy); + } + + /** + * Toggle the visibility of world axes in LoadScreen and TestScreen. + */ + void toggleAxes() { + this.isShowingAxes = !isShowingAxes; + } + + /** + * Toggle the "find best plane" option of the left-side test. + */ + void toggleFindBestPlaneLeft() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = leftTest.copyV4(); + boolean oldSetting = copy.isFindBestPlane(); + copy.setFindBestPlane(!oldSetting); + this.leftTest = getTest(copy); + } + + /** + * Toggle the "find best plane" option of the right-side test. + */ + void toggleFindBestPlaneRight() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = rightTest.copyV4(); + boolean oldSetting = copy.isFindBestPlane(); + copy.setFindBestPlane(!oldSetting); + this.rightTest = getTest(copy); + } + + /** + * Toggle the PCA setting of the left-side test. + */ + void togglePcaLeft() { + if (isRanking()) { + return; + } + VHACDParameters copy = leftTest.copyClassic(); + boolean oldSetting = copy.getPCA(); + copy.setPCA(!oldSetting); + this.leftTest = getTest(copy); + } + + /** + * Toggle the PCA setting of the right-side test. + */ + void togglePcaRight() { + if (isRanking()) { + return; + } + VHACDParameters copy = rightTest.copyClassic(); + boolean oldSetting = copy.getPCA(); + copy.setPCA(!oldSetting); + this.rightTest = getTest(copy); + } + + /** + * Toggle the shrink-wrap setting of the left-side test. + */ + void toggleShrinkLeft() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = leftTest.copyV4(); + boolean oldSetting = copy.isShrinkWrap(); + copy.setShrinkWrap(!oldSetting); + this.leftTest = getTest(copy); + } + + /** + * Toggle the shrink-wrap setting of the right-side test. + */ + void toggleShrinkRight() { + if (isRanking()) { + return; + } + Vhacd4Parameters copy = rightTest.copyV4(); + boolean oldSetting = copy.isShrinkWrap(); + copy.setShrinkWrap(!oldSetting); + this.rightTest = getTest(copy); + } + + /** + * Toggle the V-HACD version of the left-side test. + */ + void toggleVersionLeft() { + if (isRanking()) { + return; + } + this.leftTest = getToggledVersion(leftTest); + } + + /** + * Toggle the V-HACD version of the right-side test. + */ + void toggleVersionRight() { + if (isRanking()) { + return; + } + this.rightTest = getToggledVersion(rightTest); + } + + /** + * Unload the loaded C-G model, if any. + */ + void unload() { + this.rootSpatial = null; + } + // ************************************************************************* + // private methods + + /** + * Access the test results for the specified classic parameters. + * + * @param parameters the parameters (not null) + * @return a new or pre-existing test with the specified parameters + */ + DecompositionTest getTest(VHACDParameters parameters) { + DecompositionTest result = classicMap.get(parameters); + if (result == null) { + // Create a new test result. + result = new DecompositionTest(parameters); + classicMap.put(parameters, result); + } + + return result; + } + + /** + * Access the test results for the specified v4 parameters. + * + * @param parameters the parameters (not null) + * @return a new or pre-existing test with the specified parameters + */ + DecompositionTest getTest(Vhacd4Parameters parameters) { + DecompositionTest result = v4Map.get(parameters); + if (result == null) { + // Create a new test result. + result = new DecompositionTest(parameters); + v4Map.put(parameters, result); + } + + return result; + } + // ************************************************************************* + // private methods + + /** + * Construct a test with the opposite version from the specified test. + * + * @param test a test to use as a basis (not null, unaffected) + * @return a new or pre-existing test + */ + private DecompositionTest getToggledVersion(DecompositionTest test) { + DecompositionTest result; + + if (test.isClassic()) { + VHACDParameters classic = test.copyClassic(); + boolean debug = classic.getDebugEnabled(); + int maxVph = classic.getMaxVerticesPerHull(); + int resolution = classic.getVoxelResolution(); + + Vhacd4Parameters v4 = new Vhacd4Parameters(); + v4.setDebugEnabled(debug); + v4.setMaxVerticesPerHull(maxVph); + v4.setVoxelResolution(resolution); + + result = getTest(v4); + + } else { + Vhacd4Parameters v4 = test.copyV4(); + boolean debug = v4.getDebugEnabled(); + int maxVph = v4.getMaxVerticesPerHull(); + int resolution = v4.getVoxelResolution(); + + VHACDParameters classic = new VHACDParameters(); + classic.setDebugEnabled(debug); + classic.setMaxVerticesPerHull(maxVph); + classic.setVoxelResolution(resolution); + + result = getTest(classic); + } + + return result; + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/ProgressDialog.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/ProgressDialog.java index b8d578693..e1f4c6b85 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/ProgressDialog.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/ProgressDialog.java @@ -1,122 +1,122 @@ -/* - Copyright (c) 2022-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import de.lessvoid.nifty.elements.Element; -import de.lessvoid.nifty.elements.render.TextRenderer; -import java.util.logging.Logger; -import jme3utilities.nifty.dialog.DialogController; -import vhacd.VHACDProgressListener; - -/** - * Controller for the progress dialog in the VhacdTuner application. - * - * @author Stephen Gold sgold@sonic.net - */ -class ProgressDialog implements DialogController, VHACDProgressListener { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final public static Logger logger - = Logger.getLogger(ProgressDialog.class.getName()); - // ************************************************************************* - // fields - - /** - * overall completion percentage as of the latest update - */ - private double lastOP = -1.0; - /** - * message to display - */ - private String statusMessage = "0% complete"; - // ************************************************************************* - // DialogController methods - - /** - * Test whether "commit" actions are allowed. - * - * @param dialogElement (ignored) - * @return true if allowed, otherwise false - */ - @Override - public boolean allowCommit(Element dialogElement) { - return false; - } - - /** - * Construct the action-string suffix for a commit. - * - * @param dialogElement (ignored) - * @return the suffix (not null) - */ - @Override - public String commitSuffix(Element dialogElement) { - return ""; - } - - /** - * Callback to update the dialog box prior to rendering. (Invoked once per - * frame.) - * - * @param dialogElement (not null) - * @param ignored time interval between frames (in seconds, ≥0) - */ - @Override - public void update(Element dialogElement, float ignored) { - Element promptElement = dialogElement.findElementById("#prompt"); - TextRenderer textRenderer - = promptElement.getRenderer(TextRenderer.class); - textRenderer.setText(statusMessage); - } - // ************************************************************************* - // VHACDProgressListener methods - - /** - * Callback invoked (by native code) for progress updates. - * - * @param overallPercent an overall completion percentage (≥0, ≤100) - * @param stagePercent a completion percentage for the current stage (≥0, - * ≤100) - * @param operationPercent a completion percentage for the current operation - * (≥0, ≤100) - * @param stageName the name of the current stage - * @param operationName the name of the current operation - */ - @Override - public void update(double overallPercent, double stagePercent, - double operationPercent, String stageName, String operationName) { - if (overallPercent != lastOP) { - this.lastOP = overallPercent; - this.statusMessage - = String.format("%.0f%% complete", overallPercent); - } - } -} +/* + Copyright (c) 2022-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import de.lessvoid.nifty.elements.Element; +import de.lessvoid.nifty.elements.render.TextRenderer; +import java.util.logging.Logger; +import jme3utilities.nifty.dialog.DialogController; +import vhacd.VHACDProgressListener; + +/** + * Controller for the progress dialog in the VhacdTuner application. + * + * @author Stephen Gold sgold@sonic.net + */ +class ProgressDialog implements DialogController, VHACDProgressListener { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(ProgressDialog.class.getName()); + // ************************************************************************* + // fields + + /** + * overall completion percentage as of the latest update + */ + private double lastOP = -1.0; + /** + * message to display + */ + private String statusMessage = "0% complete"; + // ************************************************************************* + // DialogController methods + + /** + * Test whether "commit" actions are allowed. + * + * @param dialogElement (ignored) + * @return true if allowed, otherwise false + */ + @Override + public boolean allowCommit(Element dialogElement) { + return false; + } + + /** + * Construct the action-string suffix for a commit. + * + * @param dialogElement (ignored) + * @return the suffix (not null) + */ + @Override + public String commitSuffix(Element dialogElement) { + return ""; + } + + /** + * Callback to update the dialog box prior to rendering. (Invoked once per + * frame.) + * + * @param dialogElement (not null) + * @param ignored time interval between frames (in seconds, ≥0) + */ + @Override + public void update(Element dialogElement, float ignored) { + Element promptElement = dialogElement.findElementById("#prompt"); + TextRenderer textRenderer + = promptElement.getRenderer(TextRenderer.class); + textRenderer.setText(statusMessage); + } + // ************************************************************************* + // VHACDProgressListener methods + + /** + * Callback invoked (by native code) for progress updates. + * + * @param overallPercent an overall completion percentage (≥0, ≤100) + * @param stagePercent a completion percentage for the current stage (≥0, + * ≤100) + * @param operationPercent a completion percentage for the current operation + * (≥0, ≤100) + * @param stageName the name of the current stage + * @param operationName the name of the current operation + */ + @Override + public void update(double overallPercent, double stagePercent, + double operationPercent, String stageName, String operationName) { + if (overallPercent != lastOP) { + this.lastOP = overallPercent; + this.statusMessage + = String.format("%.0f%% complete", overallPercent); + } + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveMode.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveMode.java index d92acfb03..bb1010acc 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveMode.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveMode.java @@ -1,243 +1,243 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.export.JmeExporter; -import com.jme3.export.binary.BinaryExporter; -import com.jme3.input.KeyInput; -import com.jme3.scene.Spatial; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintStream; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.ui.ActionApplication; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "save" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class SaveMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(SaveMode.class.getName()); - /** - * asset path to the cursor for this mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - /** - * action strings specific to this input mode: - */ - final private static String asSaveJ3o = "save j3o"; - final private static String asSaveJava = "save java"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized mode. - */ - SaveMode() { - super("save"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.dumpRenderer, KeyInput.KEY_P); - bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - handled = true; - switch (actionString) { - case Action.previousScreen: - previousScreen(); - break; - - case asSaveJava: - saveJava(); - break; - - case asSaveJ3o: - saveJ3o(); - break; - - default: - handled = false; - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Go back to the TestScreen. - */ - private void previousScreen() { - setEnabled(false); - InputMode test = InputMode.findMode("test"); - test.setEnabled(true); - } - - /** - * Write the best decomposition to a file, along with the C-G model. - */ - private static void saveJ3o() { - Model model = VhacdTuner.getModel(); - DecompositionTest best = model.findRankedTest(0); - CollisionShape bestShape = best.getShape(); - RigidBodyControl rbc = new RigidBodyControl(bestShape); - - String originalPath = model.filePath(); - File originalFile = new File(originalPath); - String modelName = originalFile.getName(); - if (modelName.endsWith(".j3o")) { - modelName = MyString.removeSuffix(modelName, ".j3o"); - } else if (modelName.endsWith(".glb")) { - modelName = MyString.removeSuffix(modelName, ".glb"); - } else if (modelName.endsWith(".gltf")) { - modelName = MyString.removeSuffix(modelName, ".gltf"); - } - - String hhmmss = ActionApplication.hhmmss(); - String outputFileName = String.format("%s-%s.j3o", modelName, hhmmss); - String outputFilePath = ActionApplication.filePath(outputFileName); - - Spatial modelRoot = model.getRootSpatial(); - modelRoot = Heart.deepCopy(modelRoot); - modelRoot.addControl(rbc); - - JmeExporter exporter = BinaryExporter.getInstance(); - File outputFile = new File(outputFilePath); - SaveScreen screen = VhacdTuner.findAppState(SaveScreen.class); - assert screen.isEnabled(); - - try { - exporter.save(modelRoot, outputFile); - } catch (IOException exception) { - screen.showInfoDialog("Exception", exception.toString()); - return; - } - - // Display a confirmation dialog. - String message = String.format( - "The model and configured control have been written to%n%s.", - MyString.quote(outputFilePath)); - screen.showInfoDialog("Success", message); - } - - /** - * Write the best parameters to a file. - */ - private static void saveJava() { - Model model = VhacdTuner.getModel(); - DecompositionTest best = model.findRankedTest(0); - - String hhmmss = ActionApplication.hhmmss(); - String fileName = String.format("configure%s.java", hhmmss); - - SaveScreen screen = VhacdTuner.findAppState(SaveScreen.class); - assert screen.isEnabled(); - - String path = ActionApplication.filePath(fileName); - File file = new File(path); - try (PrintStream stream = new PrintStream(file)) { - best.write(stream); - } catch (FileNotFoundException exception) { - screen.showInfoDialog("Exception", exception.toString()); - return; - } - - // Display a confirmation dialog. - String message = String.format( - "The parameters have been written to%n%s.", - MyString.quote(path)); - screen.showInfoDialog("Success", message); - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.export.JmeExporter; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.input.KeyInput; +import com.jme3.scene.Spatial; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.ui.ActionApplication; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "save" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class SaveMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(SaveMode.class.getName()); + /** + * asset path to the cursor for this mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + /** + * action strings specific to this input mode: + */ + final private static String asSaveJ3o = "save j3o"; + final private static String asSaveJava = "save java"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized mode. + */ + SaveMode() { + super("save"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.dumpRenderer, KeyInput.KEY_P); + bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + handled = true; + switch (actionString) { + case Action.previousScreen: + previousScreen(); + break; + + case asSaveJava: + saveJava(); + break; + + case asSaveJ3o: + saveJ3o(); + break; + + default: + handled = false; + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Go back to the TestScreen. + */ + private void previousScreen() { + setEnabled(false); + InputMode test = InputMode.findMode("test"); + test.setEnabled(true); + } + + /** + * Write the best decomposition to a file, along with the C-G model. + */ + private static void saveJ3o() { + Model model = VhacdTuner.getModel(); + DecompositionTest best = model.findRankedTest(0); + CollisionShape bestShape = best.getShape(); + RigidBodyControl rbc = new RigidBodyControl(bestShape); + + String originalPath = model.filePath(); + File originalFile = new File(originalPath); + String modelName = originalFile.getName(); + if (modelName.endsWith(".j3o")) { + modelName = MyString.removeSuffix(modelName, ".j3o"); + } else if (modelName.endsWith(".glb")) { + modelName = MyString.removeSuffix(modelName, ".glb"); + } else if (modelName.endsWith(".gltf")) { + modelName = MyString.removeSuffix(modelName, ".gltf"); + } + + String hhmmss = ActionApplication.hhmmss(); + String outputFileName = String.format("%s-%s.j3o", modelName, hhmmss); + String outputFilePath = ActionApplication.filePath(outputFileName); + + Spatial modelRoot = model.getRootSpatial(); + modelRoot = Heart.deepCopy(modelRoot); + modelRoot.addControl(rbc); + + JmeExporter exporter = BinaryExporter.getInstance(); + File outputFile = new File(outputFilePath); + SaveScreen screen = VhacdTuner.findAppState(SaveScreen.class); + assert screen.isEnabled(); + + try { + exporter.save(modelRoot, outputFile); + } catch (IOException exception) { + screen.showInfoDialog("Exception", exception.toString()); + return; + } + + // Display a confirmation dialog. + String message = String.format( + "The model and configured control have been written to%n%s.", + MyString.quote(outputFilePath)); + screen.showInfoDialog("Success", message); + } + + /** + * Write the best parameters to a file. + */ + private static void saveJava() { + Model model = VhacdTuner.getModel(); + DecompositionTest best = model.findRankedTest(0); + + String hhmmss = ActionApplication.hhmmss(); + String fileName = String.format("configure%s.java", hhmmss); + + SaveScreen screen = VhacdTuner.findAppState(SaveScreen.class); + assert screen.isEnabled(); + + String path = ActionApplication.filePath(fileName); + File file = new File(path); + try (PrintStream stream = new PrintStream(file)) { + best.write(stream); + } catch (FileNotFoundException exception) { + screen.showInfoDialog("Exception", exception.toString()); + return; + } + + // Display a confirmation dialog. + String message = String.format( + "The parameters have been written to%n%s.", + MyString.quote(path)); + screen.showInfoDialog("Success", message); + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveScreen.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveScreen.java index ac734d0b8..73c75181f 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveScreen.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/SaveScreen.java @@ -1,225 +1,225 @@ -/* - Copyright (c) 2019-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import de.lessvoid.nifty.NiftyEventSubscriber; -import de.lessvoid.nifty.controls.RadioButton; -import de.lessvoid.nifty.controls.RadioButtonStateChangedEvent; -import de.lessvoid.nifty.screen.Screen; -import java.util.Map; -import java.util.logging.Logger; -import jme3utilities.InitialState; -import jme3utilities.MyString; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "save" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -public class SaveScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(SaveScreen.class.getName()); - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - SaveScreen() { - super("save", "Interface/Nifty/screens/tuner/save.xml", - InitialState.Disabled); - } - // ************************************************************************* - // new methods exposed - - /** - * Callback that Nifty invokes after a left-side radio button changes. - * - * @param buttonId Nifty element id of the radio button (not null, - * "RadioButton" suffix) - * @param event details of the event (not null) - */ - @NiftyEventSubscriber(pattern = "left.*RadioButton") - public void onLeftRadioButtonChanged(final String buttonId, - final RadioButtonStateChangedEvent event) { - if (!isIgnoreGuiChanges() && hasStarted()) { - String buttonName = MyString.removeSuffix(buttonId, "RadioButton"); - String digits = MyString.remainder(buttonName, "left"); - if (digits.equals("Other")) { - return; - } - int rank = Integer.parseInt(digits) - 1; - - Model model = VhacdTuner.getModel(); - DecompositionTest test = model.findRankedTest(rank); - if (test == null) { - test = model.getLeftTest(); - updateGroup("left", test); - } else { - model.setLeftTest(test); - } - } - } - - /** - * Callback that Nifty invokes after a right-side radio button changes. - * - * @param buttonId Nifty element id of the radio button (not null, - * "RadioButton" suffix) - * @param event details of the event (not null) - */ - @NiftyEventSubscriber(pattern = "right.*RadioButton") - public void onRightRadioButtonChanged(final String buttonId, - final RadioButtonStateChangedEvent event) { - if (!isIgnoreGuiChanges() && hasStarted()) { - String buttonName = MyString.removeSuffix(buttonId, "RadioButton"); - String digits = MyString.remainder(buttonName, "right"); - if (digits.equals("Other")) { - return; - } - int rank = Integer.parseInt(digits) - 1; - - Model model = VhacdTuner.getModel(); - DecompositionTest test = model.findRankedTest(rank); - if (test == null) { - test = model.getRightTest(); - updateGroup("right", test); - } else { - model.setRightTest(test); - } - } - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize(AppStateManager stateManager, - Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("save"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - Model model = VhacdTuner.getModel(); - DecompositionTest bestTest = model.findRankedTest(0); - Map bestParameters = bestTest.toMap(); - String bestString = bestTest.toString(); - setStatusText("best", bestString); - - StringBuilder builder = new StringBuilder(99); - int numRanked = model.countRankedTests(); - for (int rank = 1; rank < numRanked; ++rank) { - builder.setLength(0); - DecompositionTest test = model.findRankedTest(rank); - Map parameters = test.toMap(); - for (Map.Entry entry : parameters.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - Object bestValue = bestParameters.get(key); - if (value != null && !value.equals(bestValue)) { - builder.append(" ").append(key).append("=").append(value); - } - } - int cardinal = rank + 1; - setStatusText("test" + cardinal, builder.toString()); - } - - DecompositionTest leftTest = model.getLeftTest(); - updateGroup("left", leftTest); - - DecompositionTest rightTest = model.getRightTest(); - updateGroup("right", rightTest); - } - // ************************************************************************* - // private methods - - /** - * Update the left/right radio buttons. - * - * @param sideName "left" or "right" - * @param sideTest the test parameters and results for that side (not null, - * unaffected) - */ - private void updateGroup(String sideName, DecompositionTest sideTest) { - setIgnoreGuiChanges(true); - Model model = VhacdTuner.getModel(); - Screen screen = getScreen(); - boolean found = false; - - for (int rank = 0; rank < 13; ++rank) { - int cardinal = rank + 1; - String id = sideName + cardinal + "RadioButton"; - RadioButton radioButton - = screen.findNiftyControl(id, RadioButton.class); - - DecompositionTest test = model.findRankedTest(rank); - if (test == null) { - radioButton.disable(); - } else { - radioButton.enable(); - if (test == sideTest) { - radioButton.select(); - found = true; - } - } - } - - String id = sideName + "OtherRadioButton"; - RadioButton radioButton - = screen.findNiftyControl(id, RadioButton.class); - if (!found) { - radioButton.select(); - } - setIgnoreGuiChanges(false); - } -} +/* + Copyright (c) 2019-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import de.lessvoid.nifty.NiftyEventSubscriber; +import de.lessvoid.nifty.controls.RadioButton; +import de.lessvoid.nifty.controls.RadioButtonStateChangedEvent; +import de.lessvoid.nifty.screen.Screen; +import java.util.Map; +import java.util.logging.Logger; +import jme3utilities.InitialState; +import jme3utilities.MyString; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "save" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +public class SaveScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(SaveScreen.class.getName()); + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + SaveScreen() { + super("save", "Interface/Nifty/screens/tuner/save.xml", + InitialState.Disabled); + } + // ************************************************************************* + // new methods exposed + + /** + * Callback that Nifty invokes after a left-side radio button changes. + * + * @param buttonId Nifty element id of the radio button (not null, + * "RadioButton" suffix) + * @param event details of the event (not null) + */ + @NiftyEventSubscriber(pattern = "left.*RadioButton") + public void onLeftRadioButtonChanged(final String buttonId, + final RadioButtonStateChangedEvent event) { + if (!isIgnoreGuiChanges() && hasStarted()) { + String buttonName = MyString.removeSuffix(buttonId, "RadioButton"); + String digits = MyString.remainder(buttonName, "left"); + if (digits.equals("Other")) { + return; + } + int rank = Integer.parseInt(digits) - 1; + + Model model = VhacdTuner.getModel(); + DecompositionTest test = model.findRankedTest(rank); + if (test == null) { + test = model.getLeftTest(); + updateGroup("left", test); + } else { + model.setLeftTest(test); + } + } + } + + /** + * Callback that Nifty invokes after a right-side radio button changes. + * + * @param buttonId Nifty element id of the radio button (not null, + * "RadioButton" suffix) + * @param event details of the event (not null) + */ + @NiftyEventSubscriber(pattern = "right.*RadioButton") + public void onRightRadioButtonChanged(final String buttonId, + final RadioButtonStateChangedEvent event) { + if (!isIgnoreGuiChanges() && hasStarted()) { + String buttonName = MyString.removeSuffix(buttonId, "RadioButton"); + String digits = MyString.remainder(buttonName, "right"); + if (digits.equals("Other")) { + return; + } + int rank = Integer.parseInt(digits) - 1; + + Model model = VhacdTuner.getModel(); + DecompositionTest test = model.findRankedTest(rank); + if (test == null) { + test = model.getRightTest(); + updateGroup("right", test); + } else { + model.setRightTest(test); + } + } + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize(AppStateManager stateManager, + Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("save"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + Model model = VhacdTuner.getModel(); + DecompositionTest bestTest = model.findRankedTest(0); + Map bestParameters = bestTest.toMap(); + String bestString = bestTest.toString(); + setStatusText("best", bestString); + + StringBuilder builder = new StringBuilder(99); + int numRanked = model.countRankedTests(); + for (int rank = 1; rank < numRanked; ++rank) { + builder.setLength(0); + DecompositionTest test = model.findRankedTest(rank); + Map parameters = test.toMap(); + for (Map.Entry entry : parameters.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + Object bestValue = bestParameters.get(key); + if (value != null && !value.equals(bestValue)) { + builder.append(" ").append(key).append("=").append(value); + } + } + int cardinal = rank + 1; + setStatusText("test" + cardinal, builder.toString()); + } + + DecompositionTest leftTest = model.getLeftTest(); + updateGroup("left", leftTest); + + DecompositionTest rightTest = model.getRightTest(); + updateGroup("right", rightTest); + } + // ************************************************************************* + // private methods + + /** + * Update the left/right radio buttons. + * + * @param sideName "left" or "right" + * @param sideTest the test parameters and results for that side (not null, + * unaffected) + */ + private void updateGroup(String sideName, DecompositionTest sideTest) { + setIgnoreGuiChanges(true); + Model model = VhacdTuner.getModel(); + Screen screen = getScreen(); + boolean found = false; + + for (int rank = 0; rank < 13; ++rank) { + int cardinal = rank + 1; + String id = sideName + cardinal + "RadioButton"; + RadioButton radioButton + = screen.findNiftyControl(id, RadioButton.class); + + DecompositionTest test = model.findRankedTest(rank); + if (test == null) { + radioButton.disable(); + } else { + radioButton.enable(); + if (test == sideTest) { + radioButton.select(); + found = true; + } + } + } + + String id = sideName + "OtherRadioButton"; + RadioButton radioButton + = screen.findNiftyControl(id, RadioButton.class); + if (!found) { + radioButton.select(); + } + setIgnoreGuiChanges(false); + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestMode.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestMode.java index 2d346a854..1caa34c68 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestMode.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestMode.java @@ -1,927 +1,927 @@ -/* - Copyright (c) 2019-2023, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.CameraInput; -import com.jme3.input.KeyInput; -import java.util.logging.Level; -import java.util.logging.Logger; -import jme3utilities.MyString; -import jme3utilities.Validate; -import jme3utilities.nifty.dialog.AllowNull; -import jme3utilities.nifty.dialog.DoubleDialog; -import jme3utilities.nifty.dialog.IntegerDialog; -import jme3utilities.ui.InputMode; - -/** - * Input mode for the "test" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class TestMode extends InputMode { - // ************************************************************************* - // constants and loggers - - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(TestMode.class.getName()); - /** - * asset path to the cursor for this input mode - */ - final private static String assetPath = "Textures/cursors/default.cur"; - /** - * action-string prefixes specific to this input mode: - */ - final private static String apSetAlphaLeft = "set alpha left "; - final private static String apSetAlphaRight = "set alpha right "; - final private static String apSetBetaLeft = "set beta left "; - final private static String apSetBetaRight = "set beta right "; - final private static String apSetHullDSLeft = "set hullDS left "; - final private static String apSetHullDSRight = "set hullDS right "; - final private static String apSetMaxConcavityLeft - = "set maxConcavity left "; - final private static String apSetMaxConcavityRight - = "set maxConcavity right "; - final private static String apSetMaxHullsLeft = "set maxHulls left "; - final private static String apSetMaxHullsRight = "set maxHulls right "; - final private static String apSetMaxRecursionLeft - = "set maxRecursion left "; - final private static String apSetMaxRecursionRight - = "set maxRecursion right "; - final private static String apSetMaxVerticesPHLeft = "set maxVph left "; - final private static String apSetMaxVerticesPHRight = "set maxVph right "; - final private static String apSetMinEdgeLengthLeft - = "set minEdgeLength left "; - final private static String apSetMinEdgeLengthRight - = "set minEdgeLength right "; - final private static String apSetMinVolumePHLeft - = "set minVolumePH left "; - final private static String apSetMinVolumePHRight - = "set minVolumePH right "; - final private static String apSetPlaneDSLeft = "set planeDS left "; - final private static String apSetPlaneDSRight = "set planeDS right "; - final private static String apSetResolutionLeft = "set resolution left "; - final private static String apSetResolutionRight = "set resolution right "; - final private static String apSetVolumePercentErrorLeft - = "set volumePercentError left "; - final private static String apSetVolumePercentErrorRight - = "set volumePercentError right "; - /** - * action strings specific to this input mode: - */ - final private static String asLeft = "prefer left"; - final private static String asNextFillModeLeft = "next fillMode left"; - final private static String asNextFillModeRight = "next fillMode right"; - final private static String asRight = "prefer right"; - final private static String asSetAlphaLeft = "set alpha left"; - final private static String asSetAlphaRight = "set alpha right"; - final private static String asSetBetaLeft = "set beta left"; - final private static String asSetBetaRight = "set beta right"; - final private static String asSetHullDSLeft = "set hullDS left"; - final private static String asSetHullDSRight = "set hullDS right"; - final private static String asSetMaxConcavityLeft - = "set maxConcavity left"; - final private static String asSetMaxConcavityRight - = "set maxConcavity right"; - final private static String asSetMaxHullsLeft = "set maxHulls left"; - final private static String asSetMaxHullsRight = "set maxHulls right"; - final private static String asSetMaxRecursionLeft - = "set maxRecursion left"; - final private static String asSetMaxRecursionRight - = "set maxRecursion right"; - final private static String asSetMaxVerticesPHLeft - = "set maxVerticesPH left"; - final private static String asSetMaxVerticesPHRight - = "set maxVerticesPH right"; - final private static String asSetMinEdgeLengthLeft - = "set minEdgeLength left"; - final private static String asSetMinEdgeLengthRight - = "set minEdgeLength right"; - final private static String asSetMinVolumePHLeft - = "set minVolumePH left"; - final private static String asSetMinVolumePHRight - = "set minVolumePH right"; - final private static String asSetPlaneDSLeft = "set planeDS left"; - final private static String asSetPlaneDSRight = "set planeDS right"; - final private static String asSetResolutionLeft = "set resolution left"; - final private static String asSetResolutionRight = "set resolution right"; - final private static String asSetVolumePercentErrorLeft - = "set volumePercentError left"; - final private static String asSetVolumePercentErrorRight - = "set volumePercentError right"; - final private static String asStopRanking = "stop ranking"; - final private static String asToggleAsyncLeft = "toggle async left"; - final private static String asToggleAsyncRight = "toggle async right"; - final private static String asToggleFindBestPlaneLeft - = "toggle findBestPlane left"; - final private static String asToggleFindBestPlaneRight - = "toggle findBestPlane right"; - final private static String asTogglePcaLeft = "toggle pca left"; - final private static String asTogglePcaRight = "toggle pca right"; - final private static String asToggleShrinkLeft = "toggle shrink left"; - final private static String asToggleShrinkRight = "toggle shrink right"; - final private static String asToggleVersionLeft = "toggle version left"; - final private static String asToggleVersionRight = "toggle version right"; - // ************************************************************************* - // constructors - - /** - * Instantiate a disabled, uninitialized mode. - */ - TestMode() { - super("test"); - } - // ************************************************************************* - // InputMode methods - - /** - * Add default hotkey bindings. These bindings will be used if no custom - * bindings are found. - */ - @Override - protected void defaultBindings() { - bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); - bind(Action.editBindings, KeyInput.KEY_F1); - bind(Action.editDisplaySettings, KeyInput.KEY_F2); - - bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); - bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); - - bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); - bind(Action.dumpRenderer, KeyInput.KEY_P); - - bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); - bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); - bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); - bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); - bindSignal("orbitLeft", KeyInput.KEY_A); - bindSignal("orbitRight", KeyInput.KEY_D); - - bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); - bind(Action.togglePhysicsDebug, KeyInput.KEY_SLASH); - } - - /** - * Initialize this (disabled) mode prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize( - AppStateManager stateManager, Application application) { - // Configure the mouse cursor for this mode. - AssetManager manager = application.getAssetManager(); - JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); - setCursor(cursor); - - super.initialize(stateManager, application); - } - - /** - * Process an action from the keyboard or mouse. - * - * @param actionString textual description of the action (not null) - * @param ongoing true if the action is ongoing, otherwise false - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void onAction(String actionString, boolean ongoing, float tpf) { - Validate.nonNull(actionString, "action string"); - if (logger.isLoggable(Level.INFO)) { - logger.log(Level.INFO, "Got action {0} ongoing={1}", - new Object[]{MyString.quote(actionString), ongoing}); - } - - boolean handled = false; - if (ongoing) { - Model model = VhacdTuner.getModel(); - DecompositionTest leftTest = model.getLeftTest(); - DecompositionTest rightTest = model.getRightTest(); - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - - handled = true; - switch (actionString) { - case asLeft: - if (model.isRanking()) { - model.prefer(leftTest, rightTest); - } else { - model.startTest(leftTest); - } - break; - - case Action.nextScreen: - nextScreen(); - break; - - case asNextFillModeLeft: - model.nextFillModeLeft(); - break; - - case asNextFillModeRight: - model.nextFillModeRight(); - break; - - case Action.previousScreen: - previousScreen(); - break; - - case asRight: - if (model.isRanking()) { - model.prefer(rightTest, leftTest); - } else { - model.startTest(rightTest); - } - break; - - case asSetAlphaLeft: - setAlpha(leftTest, apSetAlphaLeft); - break; - - case asSetAlphaRight: - setAlpha(rightTest, apSetAlphaRight); - break; - - case asSetBetaLeft: - setBeta(leftTest, apSetBetaLeft); - break; - - case asSetBetaRight: - setBeta(rightTest, apSetBetaRight); - break; - - case asSetHullDSLeft: - setHullDS(leftTest, apSetHullDSLeft); - break; - - case asSetHullDSRight: - setHullDS(rightTest, apSetHullDSRight); - break; - - case asSetMaxConcavityLeft: - setMaxConcavity(leftTest, apSetMaxConcavityLeft); - break; - - case asSetMaxConcavityRight: - setMaxConcavity(rightTest, apSetMaxConcavityRight); - break; - - case asSetMaxHullsLeft: - setMaxHulls(leftTest, apSetMaxHullsLeft); - break; - - case asSetMaxHullsRight: - setMaxHulls(rightTest, apSetMaxHullsRight); - break; - - case asSetMaxRecursionLeft: - setMaxRecursion(leftTest, apSetMaxRecursionLeft); - break; - - case asSetMaxRecursionRight: - setMaxRecursion(rightTest, apSetMaxRecursionRight); - break; - - case asSetMaxVerticesPHLeft: - setMaxVerticesPH(leftTest, apSetMaxVerticesPHLeft); - break; - - case asSetMaxVerticesPHRight: - setMaxVerticesPH(rightTest, apSetMaxVerticesPHRight); - break; - - case asSetMinEdgeLengthLeft: - setMinEdgeLength(leftTest, apSetMinEdgeLengthLeft); - break; - - case asSetMinEdgeLengthRight: - setMinEdgeLength(rightTest, apSetMinEdgeLengthRight); - break; - - case asSetMinVolumePHLeft: - setMinVolumePH(leftTest, apSetMinVolumePHLeft); - break; - - case asSetMinVolumePHRight: - setMinVolumePH(rightTest, apSetMinVolumePHRight); - break; - - case asSetPlaneDSLeft: - setPlaneDS(leftTest, apSetPlaneDSLeft); - break; - - case asSetPlaneDSRight: - setPlaneDS(rightTest, apSetPlaneDSRight); - break; - - case asSetResolutionLeft: - setResolution(leftTest, apSetResolutionLeft); - break; - - case asSetResolutionRight: - setResolution(rightTest, apSetResolutionRight); - break; - - case asSetVolumePercentErrorLeft: - setVolumePercentError( - leftTest, apSetVolumePercentErrorLeft); - break; - - case asSetVolumePercentErrorRight: - setVolumePercentError( - rightTest, apSetVolumePercentErrorRight); - break; - - case asStopRanking: - model.stopRanking(); - break; - - case asToggleAsyncLeft: - model.toggleAsyncLeft(); - break; - - case asToggleAsyncRight: - model.toggleAsyncRight(); - break; - - case asToggleFindBestPlaneLeft: - model.toggleFindBestPlaneLeft(); - break; - - case asToggleFindBestPlaneRight: - model.toggleFindBestPlaneRight(); - break; - - case asTogglePcaLeft: - model.togglePcaLeft(); - break; - - case asTogglePcaRight: - model.togglePcaRight(); - break; - - case asToggleShrinkLeft: - model.toggleShrinkLeft(); - break; - - case asToggleShrinkRight: - model.toggleShrinkRight(); - break; - - case asToggleVersionLeft: - model.toggleVersionLeft(); - break; - - case asToggleVersionRight: - model.toggleVersionRight(); - break; - - case Action.toggleAxes: - screen.toggleShowingAxes(); - break; - - case Action.toggleMesh: - screen.toggleMesh(); - break; - - case Action.togglePhysicsDebug: - screen.togglePhysicsDebug(); - break; - - default: - handled = false; - } - if (!handled) { - handled = testForPrefixes(actionString); - } - } - if (!handled) { - getActionApplication().onAction(actionString, ongoing, tpf); - } - } - // ************************************************************************* - // private methods - - /** - * Advance to the SaveScreen if possible. - */ - private void nextScreen() { - setEnabled(false); - InputMode save = InputMode.findMode("save"); - save.setEnabled(true); - } - - /** - * Go back to the LoadScreen. - */ - private void previousScreen() { - setEnabled(false); - InputMode load = InputMode.findMode("load"); - load.setEnabled(true); - } - - /** - * Handle a "set alpha" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setAlpha(DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - double oldValue = test.copyClassic().getAlpha(); - String defaultValue = Double.toString(oldValue); - - DoubleDialog controller - = new DoubleDialog("Set", 0.0, 1.0, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog( - "Enter alpha:", defaultValue, actionPrefix, controller); - } - - /** - * Handle a "set beta" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setBeta(DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - double oldValue = test.copyClassic().getBeta(); - String defaultValue = Double.toString(oldValue); - - DoubleDialog controller - = new DoubleDialog("Set", 0.0, 1.0, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog( - "Enter beta:", defaultValue, actionPrefix, controller); - } - - /** - * Handle a "set hullDS" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setHullDS(DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.copyClassic().getConvexHullDownSampling(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 1, 16, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter convex-hull downsampling:", - defaultValue, actionPrefix, controller); - } - - /** - * Handle a "set maxConcavity" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setMaxConcavity( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - double oldValue = test.copyClassic().getMaxConcavity(); - String defaultValue = Double.toString(oldValue); - - DoubleDialog controller - = new DoubleDialog("Set", 0.0, 1.0, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter maximum concavity:", defaultValue, - actionPrefix, controller); - } - - /** - * Handle a "set maxHulls" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setMaxHulls( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.copyV4().getMaxHulls(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 1, 64, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter maximum number of hulls:", - defaultValue, actionPrefix, controller); - } - - /** - * Handle a "set maxRecursion" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setMaxRecursion( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.copyV4().getMaxRecursion(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 2, 64, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter maximum recursion:", defaultValue, - actionPrefix, controller); - } - - /** - * Handle a "set maxVerticesPH" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setMaxVerticesPH( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.maxVerticesPerHull(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 4, 2_048, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter maximum vertices per hull:", - defaultValue, actionPrefix, controller); - } - - /** - * Handle a "set minEdgeLength" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setMinEdgeLength( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.copyV4().getMinEdgeLength(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 1, 32, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter minimum edge length:", defaultValue, - actionPrefix, controller); - } - - /** - * Handle a "set minVolumePH" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setMinVolumePH( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - double oldValue = test.copyClassic().getMinVolumePerHull(); - String defaultValue = Double.toString(oldValue); - - DoubleDialog controller - = new DoubleDialog("Set", 0.0, 0.1, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter minimum volume per hull:", - defaultValue, actionPrefix, controller); - } - - /** - * Handle a "set planeDS" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setPlaneDS( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.copyClassic().getPlaneDownSampling(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 1, 16, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter plane downsampling:", defaultValue, - actionPrefix, controller); - } - - /** - * Handle a "set resolution" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setResolution( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - int oldValue = test.resolution(); - String defaultValue = Integer.toString(oldValue); - - IntegerDialog controller - = new IntegerDialog("Set", 10_000, 64_000_000, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter voxel resolution:", defaultValue, - actionPrefix, controller); - } - - /** - * Handle a "set volumePercentError" action. - * - * @param test the test to use as the base (not null, unaffected) - * @param actionPrefix the action prefix for the dialog box - */ - private static void setVolumePercentError( - DecompositionTest test, String actionPrefix) { - Model model = VhacdTuner.getModel(); - if (model.isRanking()) { - return; - } - - double oldValue = test.copyV4().getVolumePercentError(); - String defaultValue = Double.toString(oldValue); - - DoubleDialog controller - = new DoubleDialog("Set", 0.0, 100.0, AllowNull.No); - - TestScreen screen = VhacdTuner.findAppState(TestScreen.class); - assert screen.isEnabled(); - screen.closeAllPopups(); - screen.showTextEntryDialog("Enter volume percent error:", defaultValue, - actionPrefix, controller); - } - - /** - * Process an ongoing action against prefixes. - * - * @param actionString textual description of the action (not null) - * @return true if the action is handled, otherwise false - */ - private static boolean testForPrefixes(String actionString) { - Model model = VhacdTuner.getModel(); - DecompositionTest leftTest = model.getLeftTest(); - DecompositionTest rightTest = model.getRightTest(); - - boolean handled = true; - String arg; - if (actionString.startsWith(apSetAlphaLeft)) { - arg = MyString.remainder(actionString, apSetAlphaLeft); - double alpha = Double.parseDouble(arg); - DecompositionTest test = leftTest.setAlpha(alpha); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetAlphaRight)) { - arg = MyString.remainder(actionString, apSetAlphaRight); - double alpha = Double.parseDouble(arg); - DecompositionTest test = rightTest.setAlpha(alpha); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetBetaLeft)) { - arg = MyString.remainder(actionString, apSetBetaLeft); - double beta = Double.parseDouble(arg); - DecompositionTest test = leftTest.setBeta(beta); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetBetaRight)) { - arg = MyString.remainder(actionString, apSetBetaRight); - double beta = Double.parseDouble(arg); - DecompositionTest test = rightTest.setBeta(beta); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetHullDSLeft)) { - arg = MyString.remainder(actionString, apSetHullDSLeft); - int hullDS = Integer.parseInt(arg); - DecompositionTest test = leftTest.setHullDS(hullDS); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetHullDSRight)) { - arg = MyString.remainder(actionString, apSetHullDSRight); - int hullDS = Integer.parseInt(arg); - DecompositionTest test = rightTest.setHullDS(hullDS); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetMaxConcavityLeft)) { - arg = MyString.remainder(actionString, apSetMaxConcavityLeft); - double maxConcavity = Double.parseDouble(arg); - DecompositionTest test = leftTest.setMaxConcavity(maxConcavity); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetMaxConcavityRight)) { - arg = MyString.remainder(actionString, apSetMaxConcavityRight); - double maxConcavity = Double.parseDouble(arg); - DecompositionTest test = rightTest.setMaxConcavity(maxConcavity); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetMaxHullsLeft)) { - arg = MyString.remainder(actionString, apSetMaxHullsLeft); - int maxHulls = Integer.parseInt(arg); - DecompositionTest test = leftTest.setMaxHulls(maxHulls); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetMaxHullsRight)) { - arg = MyString.remainder(actionString, apSetMaxHullsRight); - int maxHulls = Integer.parseInt(arg); - DecompositionTest test = rightTest.setMaxHulls(maxHulls); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetMaxRecursionLeft)) { - arg = MyString.remainder(actionString, apSetMaxRecursionLeft); - int depth = Integer.parseInt(arg); - DecompositionTest test = leftTest.setMaxRecursion(depth); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetMaxRecursionRight)) { - arg = MyString.remainder(actionString, apSetMaxRecursionRight); - int depth = Integer.parseInt(arg); - DecompositionTest test = rightTest.setMaxRecursion(depth); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetMaxVerticesPHLeft)) { - arg = MyString.remainder(actionString, apSetMaxVerticesPHLeft); - int limit = Integer.parseInt(arg); - DecompositionTest test = leftTest.setMaxVerticesPerHull(limit); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetMaxVerticesPHRight)) { - arg = MyString.remainder(actionString, apSetMaxVerticesPHRight); - int limit = Integer.parseInt(arg); - DecompositionTest test = rightTest.setMaxVerticesPerHull(limit); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetMinEdgeLengthLeft)) { - arg = MyString.remainder(actionString, apSetMinEdgeLengthLeft); - int length = Integer.parseInt(arg); - DecompositionTest test = leftTest.setMinEdgeLength(length); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetMinEdgeLengthRight)) { - arg = MyString.remainder(actionString, apSetMinEdgeLengthRight); - int length = Integer.parseInt(arg); - DecompositionTest test = rightTest.setMinEdgeLength(length); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetMinVolumePHLeft)) { - arg = MyString.remainder(actionString, apSetMinVolumePHLeft); - double volume = Double.parseDouble(arg); - DecompositionTest test = leftTest.setMinVolumePH(volume); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetMinVolumePHRight)) { - arg = MyString.remainder(actionString, apSetMinVolumePHRight); - double volume = Double.parseDouble(arg); - DecompositionTest test = rightTest.setMinVolumePH(volume); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetPlaneDSLeft)) { - arg = MyString.remainder(actionString, apSetPlaneDSLeft); - int planeDS = Integer.parseInt(arg); - DecompositionTest test = leftTest.setPlaneDS(planeDS); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetPlaneDSRight)) { - arg = MyString.remainder(actionString, apSetPlaneDSRight); - int hullDS = Integer.parseInt(arg); - DecompositionTest test = rightTest.setPlaneDS(hullDS); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetResolutionLeft)) { - arg = MyString.remainder(actionString, apSetResolutionLeft); - int resolution = Integer.parseInt(arg); - DecompositionTest test = leftTest.setResolution(resolution); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetResolutionRight)) { - arg = MyString.remainder(actionString, apSetResolutionRight); - int resolution = Integer.parseInt(arg); - DecompositionTest test = rightTest.setResolution(resolution); - model.setRightTest(test); - - } else if (actionString.startsWith(apSetVolumePercentErrorLeft)) { - arg = MyString.remainder(actionString, apSetVolumePercentErrorLeft); - double percent = Double.parseDouble(arg); - DecompositionTest test = leftTest.setVolumePercentError(percent); - model.setLeftTest(test); - - } else if (actionString.startsWith(apSetVolumePercentErrorRight)) { - arg = MyString.remainder( - actionString, apSetVolumePercentErrorRight); - double percent = Double.parseDouble(arg); - DecompositionTest test = rightTest.setVolumePercentError(percent); - model.setRightTest(test); - - } else { - handled = false; - } - - return handled; - } -} +/* + Copyright (c) 2019-2023, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetManager; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.CameraInput; +import com.jme3.input.KeyInput; +import java.util.logging.Level; +import java.util.logging.Logger; +import jme3utilities.MyString; +import jme3utilities.Validate; +import jme3utilities.nifty.dialog.AllowNull; +import jme3utilities.nifty.dialog.DoubleDialog; +import jme3utilities.nifty.dialog.IntegerDialog; +import jme3utilities.ui.InputMode; + +/** + * Input mode for the "test" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class TestMode extends InputMode { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(TestMode.class.getName()); + /** + * asset path to the cursor for this input mode + */ + final private static String assetPath = "Textures/cursors/default.cur"; + /** + * action-string prefixes specific to this input mode: + */ + final private static String apSetAlphaLeft = "set alpha left "; + final private static String apSetAlphaRight = "set alpha right "; + final private static String apSetBetaLeft = "set beta left "; + final private static String apSetBetaRight = "set beta right "; + final private static String apSetHullDSLeft = "set hullDS left "; + final private static String apSetHullDSRight = "set hullDS right "; + final private static String apSetMaxConcavityLeft + = "set maxConcavity left "; + final private static String apSetMaxConcavityRight + = "set maxConcavity right "; + final private static String apSetMaxHullsLeft = "set maxHulls left "; + final private static String apSetMaxHullsRight = "set maxHulls right "; + final private static String apSetMaxRecursionLeft + = "set maxRecursion left "; + final private static String apSetMaxRecursionRight + = "set maxRecursion right "; + final private static String apSetMaxVerticesPHLeft = "set maxVph left "; + final private static String apSetMaxVerticesPHRight = "set maxVph right "; + final private static String apSetMinEdgeLengthLeft + = "set minEdgeLength left "; + final private static String apSetMinEdgeLengthRight + = "set minEdgeLength right "; + final private static String apSetMinVolumePHLeft + = "set minVolumePH left "; + final private static String apSetMinVolumePHRight + = "set minVolumePH right "; + final private static String apSetPlaneDSLeft = "set planeDS left "; + final private static String apSetPlaneDSRight = "set planeDS right "; + final private static String apSetResolutionLeft = "set resolution left "; + final private static String apSetResolutionRight = "set resolution right "; + final private static String apSetVolumePercentErrorLeft + = "set volumePercentError left "; + final private static String apSetVolumePercentErrorRight + = "set volumePercentError right "; + /** + * action strings specific to this input mode: + */ + final private static String asLeft = "prefer left"; + final private static String asNextFillModeLeft = "next fillMode left"; + final private static String asNextFillModeRight = "next fillMode right"; + final private static String asRight = "prefer right"; + final private static String asSetAlphaLeft = "set alpha left"; + final private static String asSetAlphaRight = "set alpha right"; + final private static String asSetBetaLeft = "set beta left"; + final private static String asSetBetaRight = "set beta right"; + final private static String asSetHullDSLeft = "set hullDS left"; + final private static String asSetHullDSRight = "set hullDS right"; + final private static String asSetMaxConcavityLeft + = "set maxConcavity left"; + final private static String asSetMaxConcavityRight + = "set maxConcavity right"; + final private static String asSetMaxHullsLeft = "set maxHulls left"; + final private static String asSetMaxHullsRight = "set maxHulls right"; + final private static String asSetMaxRecursionLeft + = "set maxRecursion left"; + final private static String asSetMaxRecursionRight + = "set maxRecursion right"; + final private static String asSetMaxVerticesPHLeft + = "set maxVerticesPH left"; + final private static String asSetMaxVerticesPHRight + = "set maxVerticesPH right"; + final private static String asSetMinEdgeLengthLeft + = "set minEdgeLength left"; + final private static String asSetMinEdgeLengthRight + = "set minEdgeLength right"; + final private static String asSetMinVolumePHLeft + = "set minVolumePH left"; + final private static String asSetMinVolumePHRight + = "set minVolumePH right"; + final private static String asSetPlaneDSLeft = "set planeDS left"; + final private static String asSetPlaneDSRight = "set planeDS right"; + final private static String asSetResolutionLeft = "set resolution left"; + final private static String asSetResolutionRight = "set resolution right"; + final private static String asSetVolumePercentErrorLeft + = "set volumePercentError left"; + final private static String asSetVolumePercentErrorRight + = "set volumePercentError right"; + final private static String asStopRanking = "stop ranking"; + final private static String asToggleAsyncLeft = "toggle async left"; + final private static String asToggleAsyncRight = "toggle async right"; + final private static String asToggleFindBestPlaneLeft + = "toggle findBestPlane left"; + final private static String asToggleFindBestPlaneRight + = "toggle findBestPlane right"; + final private static String asTogglePcaLeft = "toggle pca left"; + final private static String asTogglePcaRight = "toggle pca right"; + final private static String asToggleShrinkLeft = "toggle shrink left"; + final private static String asToggleShrinkRight = "toggle shrink right"; + final private static String asToggleVersionLeft = "toggle version left"; + final private static String asToggleVersionRight = "toggle version right"; + // ************************************************************************* + // constructors + + /** + * Instantiate a disabled, uninitialized mode. + */ + TestMode() { + super("test"); + } + // ************************************************************************* + // InputMode methods + + /** + * Add default hotkey bindings. These bindings will be used if no custom + * bindings are found. + */ + @Override + protected void defaultBindings() { + bind(SimpleApplication.INPUT_MAPPING_EXIT, KeyInput.KEY_ESCAPE); + bind(Action.editBindings, KeyInput.KEY_F1); + bind(Action.editDisplaySettings, KeyInput.KEY_F2); + + bind(Action.nextScreen, KeyInput.KEY_PGDN, KeyInput.KEY_N); + bind(Action.previousScreen, KeyInput.KEY_PGUP, KeyInput.KEY_B); + + bind(Action.dumpPhysicsSpace, KeyInput.KEY_O); + bind(Action.dumpRenderer, KeyInput.KEY_P); + + bindSignal(CameraInput.FLYCAM_BACKWARD, KeyInput.KEY_S); + bindSignal(CameraInput.FLYCAM_FORWARD, KeyInput.KEY_W); + bindSignal(CameraInput.FLYCAM_LOWER, KeyInput.KEY_Z); + bindSignal(CameraInput.FLYCAM_RISE, KeyInput.KEY_Q); + bindSignal("orbitLeft", KeyInput.KEY_A); + bindSignal("orbitRight", KeyInput.KEY_D); + + bind(SimpleApplication.INPUT_MAPPING_CAMERA_POS, KeyInput.KEY_C); + bind(Action.togglePhysicsDebug, KeyInput.KEY_SLASH); + } + + /** + * Initialize this (disabled) mode prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize( + AppStateManager stateManager, Application application) { + // Configure the mouse cursor for this mode. + AssetManager manager = application.getAssetManager(); + JmeCursor cursor = (JmeCursor) manager.loadAsset(assetPath); + setCursor(cursor); + + super.initialize(stateManager, application); + } + + /** + * Process an action from the keyboard or mouse. + * + * @param actionString textual description of the action (not null) + * @param ongoing true if the action is ongoing, otherwise false + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void onAction(String actionString, boolean ongoing, float tpf) { + Validate.nonNull(actionString, "action string"); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Got action {0} ongoing={1}", + new Object[]{MyString.quote(actionString), ongoing}); + } + + boolean handled = false; + if (ongoing) { + Model model = VhacdTuner.getModel(); + DecompositionTest leftTest = model.getLeftTest(); + DecompositionTest rightTest = model.getRightTest(); + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + + handled = true; + switch (actionString) { + case asLeft: + if (model.isRanking()) { + model.prefer(leftTest, rightTest); + } else { + model.startTest(leftTest); + } + break; + + case Action.nextScreen: + nextScreen(); + break; + + case asNextFillModeLeft: + model.nextFillModeLeft(); + break; + + case asNextFillModeRight: + model.nextFillModeRight(); + break; + + case Action.previousScreen: + previousScreen(); + break; + + case asRight: + if (model.isRanking()) { + model.prefer(rightTest, leftTest); + } else { + model.startTest(rightTest); + } + break; + + case asSetAlphaLeft: + setAlpha(leftTest, apSetAlphaLeft); + break; + + case asSetAlphaRight: + setAlpha(rightTest, apSetAlphaRight); + break; + + case asSetBetaLeft: + setBeta(leftTest, apSetBetaLeft); + break; + + case asSetBetaRight: + setBeta(rightTest, apSetBetaRight); + break; + + case asSetHullDSLeft: + setHullDS(leftTest, apSetHullDSLeft); + break; + + case asSetHullDSRight: + setHullDS(rightTest, apSetHullDSRight); + break; + + case asSetMaxConcavityLeft: + setMaxConcavity(leftTest, apSetMaxConcavityLeft); + break; + + case asSetMaxConcavityRight: + setMaxConcavity(rightTest, apSetMaxConcavityRight); + break; + + case asSetMaxHullsLeft: + setMaxHulls(leftTest, apSetMaxHullsLeft); + break; + + case asSetMaxHullsRight: + setMaxHulls(rightTest, apSetMaxHullsRight); + break; + + case asSetMaxRecursionLeft: + setMaxRecursion(leftTest, apSetMaxRecursionLeft); + break; + + case asSetMaxRecursionRight: + setMaxRecursion(rightTest, apSetMaxRecursionRight); + break; + + case asSetMaxVerticesPHLeft: + setMaxVerticesPH(leftTest, apSetMaxVerticesPHLeft); + break; + + case asSetMaxVerticesPHRight: + setMaxVerticesPH(rightTest, apSetMaxVerticesPHRight); + break; + + case asSetMinEdgeLengthLeft: + setMinEdgeLength(leftTest, apSetMinEdgeLengthLeft); + break; + + case asSetMinEdgeLengthRight: + setMinEdgeLength(rightTest, apSetMinEdgeLengthRight); + break; + + case asSetMinVolumePHLeft: + setMinVolumePH(leftTest, apSetMinVolumePHLeft); + break; + + case asSetMinVolumePHRight: + setMinVolumePH(rightTest, apSetMinVolumePHRight); + break; + + case asSetPlaneDSLeft: + setPlaneDS(leftTest, apSetPlaneDSLeft); + break; + + case asSetPlaneDSRight: + setPlaneDS(rightTest, apSetPlaneDSRight); + break; + + case asSetResolutionLeft: + setResolution(leftTest, apSetResolutionLeft); + break; + + case asSetResolutionRight: + setResolution(rightTest, apSetResolutionRight); + break; + + case asSetVolumePercentErrorLeft: + setVolumePercentError( + leftTest, apSetVolumePercentErrorLeft); + break; + + case asSetVolumePercentErrorRight: + setVolumePercentError( + rightTest, apSetVolumePercentErrorRight); + break; + + case asStopRanking: + model.stopRanking(); + break; + + case asToggleAsyncLeft: + model.toggleAsyncLeft(); + break; + + case asToggleAsyncRight: + model.toggleAsyncRight(); + break; + + case asToggleFindBestPlaneLeft: + model.toggleFindBestPlaneLeft(); + break; + + case asToggleFindBestPlaneRight: + model.toggleFindBestPlaneRight(); + break; + + case asTogglePcaLeft: + model.togglePcaLeft(); + break; + + case asTogglePcaRight: + model.togglePcaRight(); + break; + + case asToggleShrinkLeft: + model.toggleShrinkLeft(); + break; + + case asToggleShrinkRight: + model.toggleShrinkRight(); + break; + + case asToggleVersionLeft: + model.toggleVersionLeft(); + break; + + case asToggleVersionRight: + model.toggleVersionRight(); + break; + + case Action.toggleAxes: + screen.toggleShowingAxes(); + break; + + case Action.toggleMesh: + screen.toggleMesh(); + break; + + case Action.togglePhysicsDebug: + screen.togglePhysicsDebug(); + break; + + default: + handled = false; + } + if (!handled) { + handled = testForPrefixes(actionString); + } + } + if (!handled) { + getActionApplication().onAction(actionString, ongoing, tpf); + } + } + // ************************************************************************* + // private methods + + /** + * Advance to the SaveScreen if possible. + */ + private void nextScreen() { + setEnabled(false); + InputMode save = InputMode.findMode("save"); + save.setEnabled(true); + } + + /** + * Go back to the LoadScreen. + */ + private void previousScreen() { + setEnabled(false); + InputMode load = InputMode.findMode("load"); + load.setEnabled(true); + } + + /** + * Handle a "set alpha" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setAlpha(DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + double oldValue = test.copyClassic().getAlpha(); + String defaultValue = Double.toString(oldValue); + + DoubleDialog controller + = new DoubleDialog("Set", 0.0, 1.0, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog( + "Enter alpha:", defaultValue, actionPrefix, controller); + } + + /** + * Handle a "set beta" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setBeta(DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + double oldValue = test.copyClassic().getBeta(); + String defaultValue = Double.toString(oldValue); + + DoubleDialog controller + = new DoubleDialog("Set", 0.0, 1.0, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog( + "Enter beta:", defaultValue, actionPrefix, controller); + } + + /** + * Handle a "set hullDS" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setHullDS(DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.copyClassic().getConvexHullDownSampling(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 1, 16, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter convex-hull downsampling:", + defaultValue, actionPrefix, controller); + } + + /** + * Handle a "set maxConcavity" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setMaxConcavity( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + double oldValue = test.copyClassic().getMaxConcavity(); + String defaultValue = Double.toString(oldValue); + + DoubleDialog controller + = new DoubleDialog("Set", 0.0, 1.0, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter maximum concavity:", defaultValue, + actionPrefix, controller); + } + + /** + * Handle a "set maxHulls" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setMaxHulls( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.copyV4().getMaxHulls(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 1, 64, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter maximum number of hulls:", + defaultValue, actionPrefix, controller); + } + + /** + * Handle a "set maxRecursion" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setMaxRecursion( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.copyV4().getMaxRecursion(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 2, 64, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter maximum recursion:", defaultValue, + actionPrefix, controller); + } + + /** + * Handle a "set maxVerticesPH" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setMaxVerticesPH( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.maxVerticesPerHull(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 4, 2_048, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter maximum vertices per hull:", + defaultValue, actionPrefix, controller); + } + + /** + * Handle a "set minEdgeLength" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setMinEdgeLength( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.copyV4().getMinEdgeLength(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 1, 32, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter minimum edge length:", defaultValue, + actionPrefix, controller); + } + + /** + * Handle a "set minVolumePH" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setMinVolumePH( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + double oldValue = test.copyClassic().getMinVolumePerHull(); + String defaultValue = Double.toString(oldValue); + + DoubleDialog controller + = new DoubleDialog("Set", 0.0, 0.1, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter minimum volume per hull:", + defaultValue, actionPrefix, controller); + } + + /** + * Handle a "set planeDS" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setPlaneDS( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.copyClassic().getPlaneDownSampling(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 1, 16, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter plane downsampling:", defaultValue, + actionPrefix, controller); + } + + /** + * Handle a "set resolution" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setResolution( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + int oldValue = test.resolution(); + String defaultValue = Integer.toString(oldValue); + + IntegerDialog controller + = new IntegerDialog("Set", 10_000, 64_000_000, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter voxel resolution:", defaultValue, + actionPrefix, controller); + } + + /** + * Handle a "set volumePercentError" action. + * + * @param test the test to use as the base (not null, unaffected) + * @param actionPrefix the action prefix for the dialog box + */ + private static void setVolumePercentError( + DecompositionTest test, String actionPrefix) { + Model model = VhacdTuner.getModel(); + if (model.isRanking()) { + return; + } + + double oldValue = test.copyV4().getVolumePercentError(); + String defaultValue = Double.toString(oldValue); + + DoubleDialog controller + = new DoubleDialog("Set", 0.0, 100.0, AllowNull.No); + + TestScreen screen = VhacdTuner.findAppState(TestScreen.class); + assert screen.isEnabled(); + screen.closeAllPopups(); + screen.showTextEntryDialog("Enter volume percent error:", defaultValue, + actionPrefix, controller); + } + + /** + * Process an ongoing action against prefixes. + * + * @param actionString textual description of the action (not null) + * @return true if the action is handled, otherwise false + */ + private static boolean testForPrefixes(String actionString) { + Model model = VhacdTuner.getModel(); + DecompositionTest leftTest = model.getLeftTest(); + DecompositionTest rightTest = model.getRightTest(); + + boolean handled = true; + String arg; + if (actionString.startsWith(apSetAlphaLeft)) { + arg = MyString.remainder(actionString, apSetAlphaLeft); + double alpha = Double.parseDouble(arg); + DecompositionTest test = leftTest.setAlpha(alpha); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetAlphaRight)) { + arg = MyString.remainder(actionString, apSetAlphaRight); + double alpha = Double.parseDouble(arg); + DecompositionTest test = rightTest.setAlpha(alpha); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetBetaLeft)) { + arg = MyString.remainder(actionString, apSetBetaLeft); + double beta = Double.parseDouble(arg); + DecompositionTest test = leftTest.setBeta(beta); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetBetaRight)) { + arg = MyString.remainder(actionString, apSetBetaRight); + double beta = Double.parseDouble(arg); + DecompositionTest test = rightTest.setBeta(beta); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetHullDSLeft)) { + arg = MyString.remainder(actionString, apSetHullDSLeft); + int hullDS = Integer.parseInt(arg); + DecompositionTest test = leftTest.setHullDS(hullDS); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetHullDSRight)) { + arg = MyString.remainder(actionString, apSetHullDSRight); + int hullDS = Integer.parseInt(arg); + DecompositionTest test = rightTest.setHullDS(hullDS); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetMaxConcavityLeft)) { + arg = MyString.remainder(actionString, apSetMaxConcavityLeft); + double maxConcavity = Double.parseDouble(arg); + DecompositionTest test = leftTest.setMaxConcavity(maxConcavity); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetMaxConcavityRight)) { + arg = MyString.remainder(actionString, apSetMaxConcavityRight); + double maxConcavity = Double.parseDouble(arg); + DecompositionTest test = rightTest.setMaxConcavity(maxConcavity); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetMaxHullsLeft)) { + arg = MyString.remainder(actionString, apSetMaxHullsLeft); + int maxHulls = Integer.parseInt(arg); + DecompositionTest test = leftTest.setMaxHulls(maxHulls); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetMaxHullsRight)) { + arg = MyString.remainder(actionString, apSetMaxHullsRight); + int maxHulls = Integer.parseInt(arg); + DecompositionTest test = rightTest.setMaxHulls(maxHulls); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetMaxRecursionLeft)) { + arg = MyString.remainder(actionString, apSetMaxRecursionLeft); + int depth = Integer.parseInt(arg); + DecompositionTest test = leftTest.setMaxRecursion(depth); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetMaxRecursionRight)) { + arg = MyString.remainder(actionString, apSetMaxRecursionRight); + int depth = Integer.parseInt(arg); + DecompositionTest test = rightTest.setMaxRecursion(depth); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetMaxVerticesPHLeft)) { + arg = MyString.remainder(actionString, apSetMaxVerticesPHLeft); + int limit = Integer.parseInt(arg); + DecompositionTest test = leftTest.setMaxVerticesPerHull(limit); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetMaxVerticesPHRight)) { + arg = MyString.remainder(actionString, apSetMaxVerticesPHRight); + int limit = Integer.parseInt(arg); + DecompositionTest test = rightTest.setMaxVerticesPerHull(limit); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetMinEdgeLengthLeft)) { + arg = MyString.remainder(actionString, apSetMinEdgeLengthLeft); + int length = Integer.parseInt(arg); + DecompositionTest test = leftTest.setMinEdgeLength(length); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetMinEdgeLengthRight)) { + arg = MyString.remainder(actionString, apSetMinEdgeLengthRight); + int length = Integer.parseInt(arg); + DecompositionTest test = rightTest.setMinEdgeLength(length); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetMinVolumePHLeft)) { + arg = MyString.remainder(actionString, apSetMinVolumePHLeft); + double volume = Double.parseDouble(arg); + DecompositionTest test = leftTest.setMinVolumePH(volume); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetMinVolumePHRight)) { + arg = MyString.remainder(actionString, apSetMinVolumePHRight); + double volume = Double.parseDouble(arg); + DecompositionTest test = rightTest.setMinVolumePH(volume); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetPlaneDSLeft)) { + arg = MyString.remainder(actionString, apSetPlaneDSLeft); + int planeDS = Integer.parseInt(arg); + DecompositionTest test = leftTest.setPlaneDS(planeDS); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetPlaneDSRight)) { + arg = MyString.remainder(actionString, apSetPlaneDSRight); + int hullDS = Integer.parseInt(arg); + DecompositionTest test = rightTest.setPlaneDS(hullDS); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetResolutionLeft)) { + arg = MyString.remainder(actionString, apSetResolutionLeft); + int resolution = Integer.parseInt(arg); + DecompositionTest test = leftTest.setResolution(resolution); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetResolutionRight)) { + arg = MyString.remainder(actionString, apSetResolutionRight); + int resolution = Integer.parseInt(arg); + DecompositionTest test = rightTest.setResolution(resolution); + model.setRightTest(test); + + } else if (actionString.startsWith(apSetVolumePercentErrorLeft)) { + arg = MyString.remainder(actionString, apSetVolumePercentErrorLeft); + double percent = Double.parseDouble(arg); + DecompositionTest test = leftTest.setVolumePercentError(percent); + model.setLeftTest(test); + + } else if (actionString.startsWith(apSetVolumePercentErrorRight)) { + arg = MyString.remainder( + actionString, apSetVolumePercentErrorRight); + double percent = Double.parseDouble(arg); + DecompositionTest test = rightTest.setVolumePercentError(percent); + model.setRightTest(test); + + } else { + handled = false; + } + + return handled; + } +} diff --git a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestScreen.java b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestScreen.java index 246f35816..b4b9a03dc 100644 --- a/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestScreen.java +++ b/VhacdTuner/src/main/java/jme3utilities/minie/tuner/TestScreen.java @@ -1,625 +1,625 @@ -/* - Copyright (c) 2019-2022, Stephen Gold - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3utilities.minie.tuner; - -import com.jme3.app.Application; -import com.jme3.app.state.AppStateManager; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.debug.BulletDebugAppState; -import com.jme3.bullet.objects.PhysicsBody; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import de.lessvoid.nifty.controls.Button; -import de.lessvoid.nifty.elements.Element; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.logging.Logger; -import jme3utilities.Heart; -import jme3utilities.InitialState; -import jme3utilities.MyAsset; -import jme3utilities.MyCamera; -import jme3utilities.MySpatial; -import jme3utilities.nifty.GuiScreenController; -import jme3utilities.ui.InputMode; - -/** - * The screen controller for the "test" screen of VhacdTuner. - * - * @author Stephen Gold sgold@sonic.net - */ -class TestScreen extends GuiScreenController { - // ************************************************************************* - // constants and loggers - - /** - * width of the GUI (in pixels) - */ - final private static float guiWidth = 230f; - /** - * message logger for this class - */ - final static Logger logger = Logger.getLogger(TestScreen.class.getName()); - // ************************************************************************* - // fields - - /** - * element of GUI button to proceed to the next Screen - */ - private Element nextElement; - /** - * parent of the C-G model on the left side of the screen - */ - final private Node leftScene = new Node("left scene"); - /** - * parent of the C-G model on the right side of the screen - */ - final private Node rightScene = new Node("right scene"); - /** - * visualize the mesh of the C-G model on the left side of the screen - */ - private Spatial leftMeshCgm; - /** - * visualize the mesh of the C-G model on the right side of the screen - */ - private Spatial rightMeshCgm; - /** - * pre viewport for the left side of the screen - */ - private ViewPort leftView; - /** - * pre viewport for the right side of the screen - */ - private ViewPort rightView; - // ************************************************************************* - // constructors - - /** - * Instantiate an uninitialized, disabled screen that will not be enabled - * during initialization. - */ - TestScreen() { - super("test", "Interface/Nifty/screens/tuner/test.xml", - InitialState.Disabled); - } - // ************************************************************************* - // new methods exposed - - /** - * Test whether mesh rendering is disabled. - * - * @return true if rendering is disabled, otherwise false - */ - boolean areMeshesHidden() { - Spatial.CullHint cull = leftMeshCgm.getLocalCullHint(); - if (cull == Spatial.CullHint.Always) { - assert rightMeshCgm.getLocalCullHint() == Spatial.CullHint.Always; - return true; - } else { - assert rightMeshCgm.getLocalCullHint() != Spatial.CullHint.Always; - return false; - } - } - - /** - * Toggle mesh rendering off/on. - */ - void toggleMesh() { - boolean hidden = areMeshesHidden(); - if (hidden) { - leftMeshCgm.setCullHint(Spatial.CullHint.Never); - rightMeshCgm.setCullHint(Spatial.CullHint.Never); - assert !areMeshesHidden(); - } else { - leftMeshCgm.setCullHint(Spatial.CullHint.Always); - rightMeshCgm.setCullHint(Spatial.CullHint.Always); - assert areMeshesHidden(); - } - } - - /** - * Toggle physics debug visualization off/on. - */ - void togglePhysicsDebug() { - boolean enabled = VhacdTuner.isDebugEnabled(); - if (enabled) { - VhacdTuner.clearPhysicsDebug(); - } else { - VhacdTuner.setPhysicsDebug(leftView, rightView); - } - } - - /** - * Toggle world axes visualization off/on. - */ - void toggleShowingAxes() { - Model model = VhacdTuner.getModel(); - model.toggleAxes(); - - VhacdTuner.updateAxes(leftScene); - VhacdTuner.updateAxes(rightScene); - } - // ************************************************************************* - // GuiScreenController methods - - /** - * Initialize this (disabled) screen prior to its first update. - * - * @param stateManager (not null) - * @param application (not null) - */ - @Override - public void initialize(AppStateManager stateManager, - Application application) { - super.initialize(stateManager, application); - - InputMode inputMode = InputMode.findMode("test"); - assert inputMode != null; - setListener(inputMode); - inputMode.influence(this); - } - - /** - * A callback from Nifty, invoked each time this screen shuts down. - */ - @Override - public void onEndScreen() { - super.onEndScreen(); - - VhacdTuner.clearPhysicsDebug(); - boolean success = renderManager.removeMainView(leftView); - assert success; - - success = renderManager.removeMainView(rightView); - assert success; - } - - /** - * A callback from Nifty, invoked each time this screen starts up. - */ - @Override - public void onStartScreen() { - super.onStartScreen(); - - Button nextButton = getButton("next"); - if (nextButton == null) { - throw new RuntimeException("missing GUI control: nextButton"); - } - this.nextElement = nextButton.getElement(); - - // Disable the default viewport. - viewPort.setEnabled(false); - - // Clear both scenes. - MySpatial.removeAllControls(leftScene); - MySpatial.removeAllControls(rightScene); - - List children = leftScene.getChildren(); - for (Spatial child : children) { - child.removeFromParent(); - } - children = rightScene.getChildren(); - for (Spatial child : children) { - child.removeFromParent(); - } - - // Create wireframe copies of the C-G model. - Model model = VhacdTuner.getModel(); - this.leftMeshCgm = model.getRootSpatial(); - leftMeshCgm = Heart.deepCopy(leftMeshCgm); - List geometries = MySpatial.listGeometries(leftMeshCgm); - Material wireframe = MyAsset.createWireframeMaterial( - assetManager, ColorRGBA.Yellow); - wireframe.getAdditionalRenderState() - .setWireframe(true); // TODO Why is this necessary? - wireframe.setName("wireframe"); - for (Geometry geometry : geometries) { - geometry.setMaterial(wireframe); - } - this.rightMeshCgm = Heart.deepCopy(leftMeshCgm); - - ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); - - float screenWidth = cam.getWidth(); // pixels - float viewPortWidth = (screenWidth - guiWidth) / (2f * screenWidth); - - Camera leftCamera = createSideCamera("left", 0f, viewPortWidth); - this.leftView = renderManager.createMainView("left", leftCamera); - leftView.attachScene(leftScene); - leftView.setBackgroundColor(skyColor); - leftView.setClearFlags(true, true, true); - - Camera rightCamera = createSideCamera("right", 1f - viewPortWidth, 1f); - this.rightView = renderManager.createMainView("right", rightCamera); - rightView.attachScene(rightScene); - rightView.setBackgroundColor(skyColor); - rightView.setClearFlags(true, true, true); - - leftScene.attachChild(leftMeshCgm); - rightScene.attachChild(rightMeshCgm); - - VhacdTuner.updateAxes(leftScene); - VhacdTuner.updateAxes(rightScene); - - VhacdTuner.setPhysicsDebug(leftView, rightView); - } - - /** - * Update this ScreenController prior to rendering. (Invoked once per - * frame.) - * - * @param tpf time interval between frames (in seconds, ≥0) - */ - @Override - public void update(float tpf) { - super.update(tpf); - - if (!hasStarted()) { - return; - } - - Model model = VhacdTuner.getModel(); - model.pollForTaskCompletion(); - - updateCameras(); - updateFeedback(); - updateViewButtons(); - - DecompositionTest leftTest = model.getLeftTest(); - DecompositionTest rightTest = model.getRightTest(); - - PhysicsSpace leftSpace = VhacdTuner.getLeftSpace(); - updatePhysics(leftTest, leftSpace); - updateSideButtons("Left", leftTest, rightTest); - updateSideLabels("Left", leftTest); - - PhysicsSpace rightSpace = VhacdTuner.getRightSpace(); - updatePhysics(rightTest, rightSpace); - updateSideButtons("Right", rightTest, leftTest); - updateSideLabels("Right", rightTest); - - // Update the "stop ranking" button. - String stop = model.isRanking() ? "Stop ranking" : ""; - setButtonText("stopRanking", stop); - } - // ************************************************************************* - // private methods - - /** - * Instantiate a new camera for a partial-width view port. - * - * @param name the name for the camera - * @param leftEdge the left edge of the view port (≥0, <rightEdge) - * @param rightEdge the right edge of the view port (>leftEdge, ≤1) - * @return a new instance with perspective projection - */ - private Camera createSideCamera( - String name, float leftEdge, float rightEdge) { - Camera result = cam.clone(); - result.setName(name); - - float bottomEdge = 0f; - float topEdge = 1f; - result.setViewPort(leftEdge, rightEdge, bottomEdge, topEdge); - - // The frustum should have the same aspect ratio as the view port. - float yDegrees = MyCamera.yDegrees(result); - float aspectRatio = MyCamera.viewAspectRatio(result); - float near = result.getFrustumNear(); - float far = result.getFrustumFar(); - result.setFrustumPerspective(yDegrees, aspectRatio, near, far); - - return result; - } - - /** - * Move both side cameras to mimic the default camera (which is the one - * controlled by the mouse and keyboard). - */ - private void synchronizeCameras() { - Quaternion orientation = cam.getRotation(); // alias - Vector3f location = cam.getLocation(); // alias - - Camera leftCamera = leftView.getCamera(); - leftCamera.setLocation(location); - leftCamera.setRotation(orientation); - - Camera rightCamera = rightView.getCamera(); - rightCamera.setLocation(location); - rightCamera.setRotation(orientation); - } - - private void updateCameras() { - float screenWidth = cam.getWidth(); // pixels - float viewPortWidth = (screenWidth - guiWidth) / (2f * screenWidth); - - Camera leftCamera = leftView.getCamera(); - leftCamera.setViewPortRight(viewPortWidth); - - Camera rightCamera = rightView.getCamera(); - rightCamera.setViewPortLeft(1f - viewPortWidth); - - synchronizeCameras(); - } - - /** - * Update the feedback line and the "Next>" button. - */ - private void updateFeedback() { - Model model = VhacdTuner.getModel(); - boolean isRanking = model.isRanking(); - DecompositionTest leftTest = model.getLeftTest(); - boolean leftRun = leftTest.hasBeenRun(); - DecompositionTest rightTest = model.getRightTest(); - boolean rightRun = rightTest.hasBeenRun(); - - String feedback = ""; - if (model.countRankedTests() == 0) { - feedback = "You haven't run any tests yet."; - } else if (isRanking && leftRun && !model.isRanked(leftTest)) { - feedback = "You haven't ranked the left test yet."; - } else if (isRanking && rightRun && !model.isRanked(rightTest)) { - feedback = "You haven't ranked the right test yet."; - } - - setStatusText("feedback", feedback); - if (feedback.isEmpty()) { - nextElement.show(); - } else { - nextElement.hide(); - } - } - - /** - * If the specified test has generated a shape, update the specified space - * to contain a single rigid body with that shape. Otherwise, empty the - * space. - * - * @param test (not null) - * @param space (not null, may be modified) - */ - private static void updatePhysics( - DecompositionTest test, PhysicsSpace space) { - boolean haveShape = test.hasBeenRun(); - boolean isEmpty = space.isEmpty(); - if (haveShape) { - CollisionShape shape = test.getShape(); - - Collection bodies = space.getRigidBodyList(); - PhysicsRigidBody body = Heart.first(bodies); - if (body == null || body.getCollisionShape() != shape) { - space.destroy(); - - body = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); - body.setApplicationData(test); - body.setDebugMaterial(BulletDebugAppState.enableChildColoring); - space.addCollisionObject(body); - } - - } else if (!haveShape && !isEmpty) { - space.destroy(); - } - } - - /** - * Update the left/right buttons. - * - * @param side the side being updated: "Left" or "Right" - * @param test parameters and results for the side being updated (not null) - * @param otherTest parameters and results for the other side (not null) - */ - private void updateSideButtons( - String side, DecompositionTest test, DecompositionTest otherTest) { - Model model = VhacdTuner.getModel(); - - String prefer = ""; - boolean isLeft = side.equals("Left"); - if (model.isRanking()) { - prefer = isLeft ? "<-- Prefer" : "Prefer -->"; - } else if (!test.hasBeenRun()) { - boolean otherRanked = model.isRanked(otherTest); - boolean otherRun = otherTest.hasBeenRun(); - if (otherRanked || !otherRun) { - prefer = isLeft ? "<-- Run" : "Run -->"; - } - } - setButtonText("prefer" + side, prefer); - - Map map = test.toMap(); - - String alpha = ""; - if (map.containsKey("alpha")) { - alpha = map.get("alpha").toString(); - } - setButtonText("alpha" + side, alpha); - - String async = ""; - if (map.containsKey("async")) { - boolean setting = (Boolean) map.get("async"); - async = setting ? "yes" : "no"; - } - setButtonText("async" + side, async); - - String beta = ""; - if (map.containsKey("beta")) { - beta = map.get("beta").toString(); - } - setButtonText("beta" + side, beta); - - String fillMode = ""; - if (map.containsKey("fillMode")) { - fillMode = map.get("fillMode").toString(); - } - setButtonText("fillMode" + side, fillMode); - - String findBestPlane = ""; - if (map.containsKey("findBest")) { - boolean fbp = (Boolean) map.get("findBest"); - findBestPlane = fbp ? "yes" : "no"; - } - setButtonText("findBestPlane" + side, findBestPlane); - - String hullDS = ""; - if (map.containsKey("hullDS")) { - hullDS = map.get("hullDS").toString(); - } - setButtonText("hullDS" + side, hullDS); - - String maxConcavity = ""; - if (map.containsKey("maxConcavity")) { - maxConcavity = map.get("maxConcavity").toString(); - } - setButtonText("maxConcavity" + side, maxConcavity); - - String maxHulls = ""; - if (map.containsKey("maxHulls")) { - maxHulls = map.get("maxHulls").toString(); - } - setButtonText("maxHulls" + side, maxHulls); - - String maxRecursion = ""; - if (map.containsKey("maxRecursion")) { - maxRecursion = map.get("maxRecursion").toString(); - } - setButtonText("maxRecursion" + side, maxRecursion); - - String maxVerticesPH = map.get("maxVerticesPH").toString(); - setButtonText("maxVerticesPH" + side, maxVerticesPH); - - String minEdgeLength = ""; - if (map.containsKey("minEdge")) { - minEdgeLength = map.get("minEdge").toString(); - } - setButtonText("minEdgeLength" + side, minEdgeLength); - - String minVolumePH = ""; - if (map.containsKey("minVolumePH")) { - minVolumePH = map.get("minVolumePH").toString(); - } - setButtonText("minVolumePH" + side, minVolumePH); - - String pca = ""; - if (map.containsKey("PCA")) { - boolean setting = (Boolean) map.get("PCA"); - pca = setting ? "yes" : "no"; - } - setButtonText("pca" + side, pca); - - String planeDS = ""; - if (map.containsKey("planeDS")) { - planeDS = map.get("planeDS").toString(); - } - setButtonText("planeDS" + side, planeDS); - - String resolution = map.get("resolution").toString(); - setButtonText("resolution" + side, resolution); - - String shrinkWrap = ""; - if (map.containsKey("shrink")) { - boolean shrink = (Boolean) map.get("shrink"); - shrinkWrap = shrink ? "yes" : "no"; - } - setButtonText("shrink" + side, shrinkWrap); - - boolean isClassic = (Boolean) map.get("classic"); - String version = isClassic ? "classic" : "v4"; - setButtonText("version" + side, version); - - String volumeError = ""; - if (map.containsKey("volumeErr")) { - volumeError = map.get("volumeErr").toString() + "%"; - } - setButtonText("volumeError" + side, volumeError); - } - - /** - * Update the left/right status labels. - * - * @param sideName "Left" or "Right" - * @param test the parameters and results for that side (not null, - * unaffected) - */ - private void updateSideLabels(String sideName, DecompositionTest test) { - String hulls = "?"; - String rank = "?"; - String seconds = "?"; - String vertices = "?"; - - if (test.hasBeenRun()) { - int h = test.getShape().countChildren(); - hulls = Integer.toString(h); - - float latency = test.latency(); - seconds = String.format("%.2f", latency); - - int numVertices = test.countVertices(); - vertices = Integer.toString(numVertices); - - Model model = VhacdTuner.getModel(); - int r = model.findRank(test); - if (r >= 0) { - int cardinal = r + 1; - rank = "#" + cardinal; - } - } - - setStatusText("hulls" + sideName, hulls); - setStatusText("rank" + sideName, rank); - setStatusText("seconds" + sideName, seconds); - setStatusText("vertices" + sideName, vertices); - } - - /** - * Update the buttons that toggle view elements. - */ - private void updateViewButtons() { - String debugButton = ""; - Model model = VhacdTuner.getModel(); - boolean leftRun = model.getLeftTest().hasBeenRun(); - boolean rightRun = model.getRightTest().hasBeenRun(); - if (leftRun || rightRun) { - boolean isDebug = VhacdTuner.isDebugEnabled(); - debugButton = isDebug ? "Hide hulls" : "Show hulls"; - } - setButtonText("debug", debugButton); - - String meshButton = areMeshesHidden() ? "Show mesh" : "Hide mesh"; - setButtonText("mesh", meshButton); - - String axesText = model.isShowingAxes() ? "Hide axes" : "Show axes"; - setButtonText("axes", axesText); - } -} +/* + Copyright (c) 2019-2022, Stephen Gold + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3utilities.minie.tuner; + +import com.jme3.app.Application; +import com.jme3.app.state.AppStateManager; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.debug.BulletDebugAppState; +import com.jme3.bullet.objects.PhysicsBody; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import de.lessvoid.nifty.controls.Button; +import de.lessvoid.nifty.elements.Element; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import jme3utilities.Heart; +import jme3utilities.InitialState; +import jme3utilities.MyAsset; +import jme3utilities.MyCamera; +import jme3utilities.MySpatial; +import jme3utilities.nifty.GuiScreenController; +import jme3utilities.ui.InputMode; + +/** + * The screen controller for the "test" screen of VhacdTuner. + * + * @author Stephen Gold sgold@sonic.net + */ +class TestScreen extends GuiScreenController { + // ************************************************************************* + // constants and loggers + + /** + * width of the GUI (in pixels) + */ + final private static float guiWidth = 230f; + /** + * message logger for this class + */ + final static Logger logger = Logger.getLogger(TestScreen.class.getName()); + // ************************************************************************* + // fields + + /** + * element of GUI button to proceed to the next Screen + */ + private Element nextElement; + /** + * parent of the C-G model on the left side of the screen + */ + final private Node leftScene = new Node("left scene"); + /** + * parent of the C-G model on the right side of the screen + */ + final private Node rightScene = new Node("right scene"); + /** + * visualize the mesh of the C-G model on the left side of the screen + */ + private Spatial leftMeshCgm; + /** + * visualize the mesh of the C-G model on the right side of the screen + */ + private Spatial rightMeshCgm; + /** + * pre viewport for the left side of the screen + */ + private ViewPort leftView; + /** + * pre viewport for the right side of the screen + */ + private ViewPort rightView; + // ************************************************************************* + // constructors + + /** + * Instantiate an uninitialized, disabled screen that will not be enabled + * during initialization. + */ + TestScreen() { + super("test", "Interface/Nifty/screens/tuner/test.xml", + InitialState.Disabled); + } + // ************************************************************************* + // new methods exposed + + /** + * Test whether mesh rendering is disabled. + * + * @return true if rendering is disabled, otherwise false + */ + boolean areMeshesHidden() { + Spatial.CullHint cull = leftMeshCgm.getLocalCullHint(); + if (cull == Spatial.CullHint.Always) { + assert rightMeshCgm.getLocalCullHint() == Spatial.CullHint.Always; + return true; + } else { + assert rightMeshCgm.getLocalCullHint() != Spatial.CullHint.Always; + return false; + } + } + + /** + * Toggle mesh rendering off/on. + */ + void toggleMesh() { + boolean hidden = areMeshesHidden(); + if (hidden) { + leftMeshCgm.setCullHint(Spatial.CullHint.Never); + rightMeshCgm.setCullHint(Spatial.CullHint.Never); + assert !areMeshesHidden(); + } else { + leftMeshCgm.setCullHint(Spatial.CullHint.Always); + rightMeshCgm.setCullHint(Spatial.CullHint.Always); + assert areMeshesHidden(); + } + } + + /** + * Toggle physics debug visualization off/on. + */ + void togglePhysicsDebug() { + boolean enabled = VhacdTuner.isDebugEnabled(); + if (enabled) { + VhacdTuner.clearPhysicsDebug(); + } else { + VhacdTuner.setPhysicsDebug(leftView, rightView); + } + } + + /** + * Toggle world axes visualization off/on. + */ + void toggleShowingAxes() { + Model model = VhacdTuner.getModel(); + model.toggleAxes(); + + VhacdTuner.updateAxes(leftScene); + VhacdTuner.updateAxes(rightScene); + } + // ************************************************************************* + // GuiScreenController methods + + /** + * Initialize this (disabled) screen prior to its first update. + * + * @param stateManager (not null) + * @param application (not null) + */ + @Override + public void initialize(AppStateManager stateManager, + Application application) { + super.initialize(stateManager, application); + + InputMode inputMode = InputMode.findMode("test"); + assert inputMode != null; + setListener(inputMode); + inputMode.influence(this); + } + + /** + * A callback from Nifty, invoked each time this screen shuts down. + */ + @Override + public void onEndScreen() { + super.onEndScreen(); + + VhacdTuner.clearPhysicsDebug(); + boolean success = renderManager.removeMainView(leftView); + assert success; + + success = renderManager.removeMainView(rightView); + assert success; + } + + /** + * A callback from Nifty, invoked each time this screen starts up. + */ + @Override + public void onStartScreen() { + super.onStartScreen(); + + Button nextButton = getButton("next"); + if (nextButton == null) { + throw new RuntimeException("missing GUI control: nextButton"); + } + this.nextElement = nextButton.getElement(); + + // Disable the default viewport. + viewPort.setEnabled(false); + + // Clear both scenes. + MySpatial.removeAllControls(leftScene); + MySpatial.removeAllControls(rightScene); + + List children = leftScene.getChildren(); + for (Spatial child : children) { + child.removeFromParent(); + } + children = rightScene.getChildren(); + for (Spatial child : children) { + child.removeFromParent(); + } + + // Create wireframe copies of the C-G model. + Model model = VhacdTuner.getModel(); + this.leftMeshCgm = model.getRootSpatial(); + leftMeshCgm = Heart.deepCopy(leftMeshCgm); + List geometries = MySpatial.listGeometries(leftMeshCgm); + Material wireframe = MyAsset.createWireframeMaterial( + assetManager, ColorRGBA.Yellow); + wireframe.getAdditionalRenderState() + .setWireframe(true); // TODO Why is this necessary? + wireframe.setName("wireframe"); + for (Geometry geometry : geometries) { + geometry.setMaterial(wireframe); + } + this.rightMeshCgm = Heart.deepCopy(leftMeshCgm); + + ColorRGBA skyColor = new ColorRGBA(0.1f, 0.2f, 0.4f, 1f); + + float screenWidth = cam.getWidth(); // pixels + float viewPortWidth = (screenWidth - guiWidth) / (2f * screenWidth); + + Camera leftCamera = createSideCamera("left", 0f, viewPortWidth); + this.leftView = renderManager.createMainView("left", leftCamera); + leftView.attachScene(leftScene); + leftView.setBackgroundColor(skyColor); + leftView.setClearFlags(true, true, true); + + Camera rightCamera = createSideCamera("right", 1f - viewPortWidth, 1f); + this.rightView = renderManager.createMainView("right", rightCamera); + rightView.attachScene(rightScene); + rightView.setBackgroundColor(skyColor); + rightView.setClearFlags(true, true, true); + + leftScene.attachChild(leftMeshCgm); + rightScene.attachChild(rightMeshCgm); + + VhacdTuner.updateAxes(leftScene); + VhacdTuner.updateAxes(rightScene); + + VhacdTuner.setPhysicsDebug(leftView, rightView); + } + + /** + * Update this ScreenController prior to rendering. (Invoked once per + * frame.) + * + * @param tpf time interval between frames (in seconds, ≥0) + */ + @Override + public void update(float tpf) { + super.update(tpf); + + if (!hasStarted()) { + return; + } + + Model model = VhacdTuner.getModel(); + model.pollForTaskCompletion(); + + updateCameras(); + updateFeedback(); + updateViewButtons(); + + DecompositionTest leftTest = model.getLeftTest(); + DecompositionTest rightTest = model.getRightTest(); + + PhysicsSpace leftSpace = VhacdTuner.getLeftSpace(); + updatePhysics(leftTest, leftSpace); + updateSideButtons("Left", leftTest, rightTest); + updateSideLabels("Left", leftTest); + + PhysicsSpace rightSpace = VhacdTuner.getRightSpace(); + updatePhysics(rightTest, rightSpace); + updateSideButtons("Right", rightTest, leftTest); + updateSideLabels("Right", rightTest); + + // Update the "stop ranking" button. + String stop = model.isRanking() ? "Stop ranking" : ""; + setButtonText("stopRanking", stop); + } + // ************************************************************************* + // private methods + + /** + * Instantiate a new camera for a partial-width view port. + * + * @param name the name for the camera + * @param leftEdge the left edge of the view port (≥0, <rightEdge) + * @param rightEdge the right edge of the view port (>leftEdge, ≤1) + * @return a new instance with perspective projection + */ + private Camera createSideCamera( + String name, float leftEdge, float rightEdge) { + Camera result = cam.clone(); + result.setName(name); + + float bottomEdge = 0f; + float topEdge = 1f; + result.setViewPort(leftEdge, rightEdge, bottomEdge, topEdge); + + // The frustum should have the same aspect ratio as the view port. + float yDegrees = MyCamera.yDegrees(result); + float aspectRatio = MyCamera.viewAspectRatio(result); + float near = result.getFrustumNear(); + float far = result.getFrustumFar(); + result.setFrustumPerspective(yDegrees, aspectRatio, near, far); + + return result; + } + + /** + * Move both side cameras to mimic the default camera (which is the one + * controlled by the mouse and keyboard). + */ + private void synchronizeCameras() { + Quaternion orientation = cam.getRotation(); // alias + Vector3f location = cam.getLocation(); // alias + + Camera leftCamera = leftView.getCamera(); + leftCamera.setLocation(location); + leftCamera.setRotation(orientation); + + Camera rightCamera = rightView.getCamera(); + rightCamera.setLocation(location); + rightCamera.setRotation(orientation); + } + + private void updateCameras() { + float screenWidth = cam.getWidth(); // pixels + float viewPortWidth = (screenWidth - guiWidth) / (2f * screenWidth); + + Camera leftCamera = leftView.getCamera(); + leftCamera.setViewPortRight(viewPortWidth); + + Camera rightCamera = rightView.getCamera(); + rightCamera.setViewPortLeft(1f - viewPortWidth); + + synchronizeCameras(); + } + + /** + * Update the feedback line and the "Next>" button. + */ + private void updateFeedback() { + Model model = VhacdTuner.getModel(); + boolean isRanking = model.isRanking(); + DecompositionTest leftTest = model.getLeftTest(); + boolean leftRun = leftTest.hasBeenRun(); + DecompositionTest rightTest = model.getRightTest(); + boolean rightRun = rightTest.hasBeenRun(); + + String feedback = ""; + if (model.countRankedTests() == 0) { + feedback = "You haven't run any tests yet."; + } else if (isRanking && leftRun && !model.isRanked(leftTest)) { + feedback = "You haven't ranked the left test yet."; + } else if (isRanking && rightRun && !model.isRanked(rightTest)) { + feedback = "You haven't ranked the right test yet."; + } + + setStatusText("feedback", feedback); + if (feedback.isEmpty()) { + nextElement.show(); + } else { + nextElement.hide(); + } + } + + /** + * If the specified test has generated a shape, update the specified space + * to contain a single rigid body with that shape. Otherwise, empty the + * space. + * + * @param test (not null) + * @param space (not null, may be modified) + */ + private static void updatePhysics( + DecompositionTest test, PhysicsSpace space) { + boolean haveShape = test.hasBeenRun(); + boolean isEmpty = space.isEmpty(); + if (haveShape) { + CollisionShape shape = test.getShape(); + + Collection bodies = space.getRigidBodyList(); + PhysicsRigidBody body = Heart.first(bodies); + if (body == null || body.getCollisionShape() != shape) { + space.destroy(); + + body = new PhysicsRigidBody(shape, PhysicsBody.massForStatic); + body.setApplicationData(test); + body.setDebugMaterial(BulletDebugAppState.enableChildColoring); + space.addCollisionObject(body); + } + + } else if (!haveShape && !isEmpty) { + space.destroy(); + } + } + + /** + * Update the left/right buttons. + * + * @param side the side being updated: "Left" or "Right" + * @param test parameters and results for the side being updated (not null) + * @param otherTest parameters and results for the other side (not null) + */ + private void updateSideButtons( + String side, DecompositionTest test, DecompositionTest otherTest) { + Model model = VhacdTuner.getModel(); + + String prefer = ""; + boolean isLeft = side.equals("Left"); + if (model.isRanking()) { + prefer = isLeft ? "<-- Prefer" : "Prefer -->"; + } else if (!test.hasBeenRun()) { + boolean otherRanked = model.isRanked(otherTest); + boolean otherRun = otherTest.hasBeenRun(); + if (otherRanked || !otherRun) { + prefer = isLeft ? "<-- Run" : "Run -->"; + } + } + setButtonText("prefer" + side, prefer); + + Map map = test.toMap(); + + String alpha = ""; + if (map.containsKey("alpha")) { + alpha = map.get("alpha").toString(); + } + setButtonText("alpha" + side, alpha); + + String async = ""; + if (map.containsKey("async")) { + boolean setting = (Boolean) map.get("async"); + async = setting ? "yes" : "no"; + } + setButtonText("async" + side, async); + + String beta = ""; + if (map.containsKey("beta")) { + beta = map.get("beta").toString(); + } + setButtonText("beta" + side, beta); + + String fillMode = ""; + if (map.containsKey("fillMode")) { + fillMode = map.get("fillMode").toString(); + } + setButtonText("fillMode" + side, fillMode); + + String findBestPlane = ""; + if (map.containsKey("findBest")) { + boolean fbp = (Boolean) map.get("findBest"); + findBestPlane = fbp ? "yes" : "no"; + } + setButtonText("findBestPlane" + side, findBestPlane); + + String hullDS = ""; + if (map.containsKey("hullDS")) { + hullDS = map.get("hullDS").toString(); + } + setButtonText("hullDS" + side, hullDS); + + String maxConcavity = ""; + if (map.containsKey("maxConcavity")) { + maxConcavity = map.get("maxConcavity").toString(); + } + setButtonText("maxConcavity" + side, maxConcavity); + + String maxHulls = ""; + if (map.containsKey("maxHulls")) { + maxHulls = map.get("maxHulls").toString(); + } + setButtonText("maxHulls" + side, maxHulls); + + String maxRecursion = ""; + if (map.containsKey("maxRecursion")) { + maxRecursion = map.get("maxRecursion").toString(); + } + setButtonText("maxRecursion" + side, maxRecursion); + + String maxVerticesPH = map.get("maxVerticesPH").toString(); + setButtonText("maxVerticesPH" + side, maxVerticesPH); + + String minEdgeLength = ""; + if (map.containsKey("minEdge")) { + minEdgeLength = map.get("minEdge").toString(); + } + setButtonText("minEdgeLength" + side, minEdgeLength); + + String minVolumePH = ""; + if (map.containsKey("minVolumePH")) { + minVolumePH = map.get("minVolumePH").toString(); + } + setButtonText("minVolumePH" + side, minVolumePH); + + String pca = ""; + if (map.containsKey("PCA")) { + boolean setting = (Boolean) map.get("PCA"); + pca = setting ? "yes" : "no"; + } + setButtonText("pca" + side, pca); + + String planeDS = ""; + if (map.containsKey("planeDS")) { + planeDS = map.get("planeDS").toString(); + } + setButtonText("planeDS" + side, planeDS); + + String resolution = map.get("resolution").toString(); + setButtonText("resolution" + side, resolution); + + String shrinkWrap = ""; + if (map.containsKey("shrink")) { + boolean shrink = (Boolean) map.get("shrink"); + shrinkWrap = shrink ? "yes" : "no"; + } + setButtonText("shrink" + side, shrinkWrap); + + boolean isClassic = (Boolean) map.get("classic"); + String version = isClassic ? "classic" : "v4"; + setButtonText("version" + side, version); + + String volumeError = ""; + if (map.containsKey("volumeErr")) { + volumeError = map.get("volumeErr").toString() + "%"; + } + setButtonText("volumeError" + side, volumeError); + } + + /** + * Update the left/right status labels. + * + * @param sideName "Left" or "Right" + * @param test the parameters and results for that side (not null, + * unaffected) + */ + private void updateSideLabels(String sideName, DecompositionTest test) { + String hulls = "?"; + String rank = "?"; + String seconds = "?"; + String vertices = "?"; + + if (test.hasBeenRun()) { + int h = test.getShape().countChildren(); + hulls = Integer.toString(h); + + float latency = test.latency(); + seconds = String.format("%.2f", latency); + + int numVertices = test.countVertices(); + vertices = Integer.toString(numVertices); + + Model model = VhacdTuner.getModel(); + int r = model.findRank(test); + if (r >= 0) { + int cardinal = r + 1; + rank = "#" + cardinal; + } + } + + setStatusText("hulls" + sideName, hulls); + setStatusText("rank" + sideName, rank); + setStatusText("seconds" + sideName, seconds); + setStatusText("vertices" + sideName, vertices); + } + + /** + * Update the buttons that toggle view elements. + */ + private void updateViewButtons() { + String debugButton = ""; + Model model = VhacdTuner.getModel(); + boolean leftRun = model.getLeftTest().hasBeenRun(); + boolean rightRun = model.getRightTest().hasBeenRun(); + if (leftRun || rightRun) { + boolean isDebug = VhacdTuner.isDebugEnabled(); + debugButton = isDebug ? "Hide hulls" : "Show hulls"; + } + setButtonText("debug", debugButton); + + String meshButton = areMeshesHidden() ? "Show mesh" : "Hide mesh"; + setButtonText("mesh", meshButton); + + String axesText = model.isShowingAxes() ? "Hide axes" : "Show axes"; + setButtonText("axes", axesText); + } +} diff --git a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/filePath.xml b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/filePath.xml index 22eb835c7..1f1082d0a 100644 --- a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/filePath.xml +++ b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/filePath.xml @@ -1,77 +1,77 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/load.xml b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/load.xml index b58c36cb0..1eeaff74d 100644 --- a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/load.xml +++ b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/load.xml @@ -1,107 +1,107 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/save.xml b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/save.xml index 77fced68b..e85a55cb3 100644 --- a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/save.xml +++ b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/save.xml @@ -1,303 +1,303 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - /> - - /> - - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - /> - - /> - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + /> + + /> + + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + /> + + /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/test.xml b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/test.xml index 17cffa3b3..6f2629efc 100644 --- a/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/test.xml +++ b/VhacdTuner/src/main/resources/Interface/Nifty/screens/tuner/test.xml @@ -1,365 +1,365 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VhacdTuner/src/main/resources/Textures/icons/license.txt b/VhacdTuner/src/main/resources/Textures/icons/license.txt index e2cc9eeae..06e4cd8ba 100644 --- a/VhacdTuner/src/main/resources/Textures/icons/license.txt +++ b/VhacdTuner/src/main/resources/Textures/icons/license.txt @@ -1,21 +1,21 @@ -Licensing history for assets in src/main/resources/Textures/icons - -Stephen Gold authored "ellipsis.png", "folder.png", and "jme.png". -He makes them available under the Creative Commons -Attribution-Share Alike 3.0 Unported license -- see -https://creativecommons.org/licenses/by-sa/3.0/ for details. - -When sharing or reusing these files, please attribute them to "Stephen Gold's -Minie Project at https://github.com/stephengold/Minie" - -It is easier to ask permission than forgiveness. To request a custom license -granting rights not included in the CC-BY-SA terms, send an e-mail to -sgold@sonic.net with "custom license request" in the subject. Please -clearly indicate: - (1) your legal name - (2) which assets you want to license - (3) name and brief description of your project, if applicable - (4) the licensee (person or organization that will hold the custom license) - (5) what rights you are requesting (i.e. "non-exclusive and non-transferable - perpetual license to incorporate into proprietary software with worldwide +Licensing history for assets in src/main/resources/Textures/icons + +Stephen Gold authored "ellipsis.png", "folder.png", and "jme.png". +He makes them available under the Creative Commons +Attribution-Share Alike 3.0 Unported license -- see +https://creativecommons.org/licenses/by-sa/3.0/ for details. + +When sharing or reusing these files, please attribute them to "Stephen Gold's +Minie Project at https://github.com/stephengold/Minie" + +It is easier to ask permission than forgiveness. To request a custom license +granting rights not included in the CC-BY-SA terms, send an e-mail to +sgold@sonic.net with "custom license request" in the subject. Please +clearly indicate: + (1) your legal name + (2) which assets you want to license + (3) name and brief description of your project, if applicable + (4) the licensee (person or organization that will hold the custom license) + (5) what rights you are requesting (i.e. "non-exclusive and non-transferable + perpetual license to incorporate into proprietary software with worldwide distribution") \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5b59ab5cf..bfb6560ac 100644 --- a/build.gradle +++ b/build.gradle @@ -1,63 +1,63 @@ -// Gradle build script for the Minie project - -plugins { - id 'base' // to add a "clean" task to the root project -} - -ext { - jmeTarget = '' // distinguish non-JME libraries built for specific JME releases - - // current version of the JMonkeyEngine libraries: - jme3Version = '3.6.1-stable' - - // module coordinates of external dependencies: - acorusCoordinates = 'com.github.stephengold:Acorus:1.1.0' + jmeTarget - desktopCoordinates = 'org.jmonkeyengine:jme3-desktop:' + jme3Version - heartCoordinates = 'com.github.stephengold:Heart:8.7.0' + jmeTarget - joggCoordinates = 'org.jmonkeyengine:jme3-jogg:' + jme3Version - junitCoordinates = 'junit:junit:4.13.2' - niftyCoordinates = 'com.github.stephengold:jme3-utilities-nifty:0.9.35' + jmeTarget - pluginsCoordinates = 'org.jmonkeyengine:jme3-plugins:' + jme3Version - styleCoordinates = 'com.github.nifty-gui:nifty-style-black:1.4.3' - testdataCoordinates = 'org.jmonkeyengine:jme3-testdata:3.1.0-stable' - wesCoordinates = 'com.github.stephengold:Wes:0.7.5' + jmeTarget - - // select one version of LWJGL: - //lwjglCoordinates = 'org.jmonkeyengine:jme3-lwjgl:' + jme3Version // for LWJGL 2.x - lwjglCoordinates = 'org.jmonkeyengine:jme3-lwjgl3:' + jme3Version // for LWJGL 3.x - - minieSnapshot = '-SNAPSHOT' // for development builds - //minieSnapshot = '' // for release builds - minieVersion = '7.7.1' -} - -subprojects { - apply from: rootProject.file('common.gradle') -} - -tasks.register('checkstyle') { - dependsOn ':DacWizard:checkstyleMain', ':Jme3Examples:checkstyleMain', \ - ':MinieAssets:checkstyleMain', ':MinieDump:checkstyleMain', \ - ':MinieExamples:checkstyleMain', ':MinieLibrary:checkstyleMain', \ - ':MinieLibrary:checkstyleTest', ':TutorialApps:checkstyleMain', \ - ':VhacdTuner:checkstyleMain' - description 'Checks the style of all Java sourcecode.' -} - -// cleanup tasks: - -clean.dependsOn('cleanNodeModules') -tasks.register('cleanNodeModules', Delete) { - delete 'node_modules' -} - -// publishing tasks: - -tasks.register('install') { - dependsOn ':MinieLibrary:install' - description 'Installs Maven artifacts to the local repository.' -} -tasks.register('release') { - dependsOn ':MinieLibrary:release' - description 'Stages Maven artifacts to Sonatype OSSRH.' -} +// Gradle build script for the Minie project + +plugins { + id 'base' // to add a "clean" task to the root project +} + +ext { + jmeTarget = '' // distinguish non-JME libraries built for specific JME releases + + // current version of the JMonkeyEngine libraries: + jme3Version = '3.6.1-stable' + + // module coordinates of external dependencies: + acorusCoordinates = 'com.github.stephengold:Acorus:1.1.0' + jmeTarget + desktopCoordinates = 'org.jmonkeyengine:jme3-desktop:' + jme3Version + heartCoordinates = 'com.github.stephengold:Heart:8.7.0' + jmeTarget + joggCoordinates = 'org.jmonkeyengine:jme3-jogg:' + jme3Version + junitCoordinates = 'junit:junit:4.13.2' + niftyCoordinates = 'com.github.stephengold:jme3-utilities-nifty:0.9.35' + jmeTarget + pluginsCoordinates = 'org.jmonkeyengine:jme3-plugins:' + jme3Version + styleCoordinates = 'com.github.nifty-gui:nifty-style-black:1.4.3' + testdataCoordinates = 'org.jmonkeyengine:jme3-testdata:3.1.0-stable' + wesCoordinates = 'com.github.stephengold:Wes:0.7.5' + jmeTarget + + // select one version of LWJGL: + //lwjglCoordinates = 'org.jmonkeyengine:jme3-lwjgl:' + jme3Version // for LWJGL 2.x + lwjglCoordinates = 'org.jmonkeyengine:jme3-lwjgl3:' + jme3Version // for LWJGL 3.x + + minieSnapshot = '-SNAPSHOT' // for development builds + //minieSnapshot = '' // for release builds + minieVersion = '7.7.1' +} + +subprojects { + apply from: rootProject.file('common.gradle') +} + +tasks.register('checkstyle') { + dependsOn ':DacWizard:checkstyleMain', ':Jme3Examples:checkstyleMain', \ + ':MinieAssets:checkstyleMain', ':MinieDump:checkstyleMain', \ + ':MinieExamples:checkstyleMain', ':MinieLibrary:checkstyleMain', \ + ':MinieLibrary:checkstyleTest', ':TutorialApps:checkstyleMain', \ + ':VhacdTuner:checkstyleMain' + description 'Checks the style of all Java sourcecode.' +} + +// cleanup tasks: + +clean.dependsOn('cleanNodeModules') +tasks.register('cleanNodeModules', Delete) { + delete 'node_modules' +} + +// publishing tasks: + +tasks.register('install') { + dependsOn ':MinieLibrary:install' + description 'Installs Maven artifacts to the local repository.' +} +tasks.register('release') { + dependsOn ':MinieLibrary:release' + description 'Stages Maven artifacts to Sonatype OSSRH.' +} diff --git a/settings.gradle b/settings.gradle index 404cb4a38..62f776899 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,31 +1,31 @@ -/* - * settings.gradle: global settings shared by all Minie subprojects - */ - -dependencyResolutionManagement { - repositories { - //mavenLocal() // to find local SNAPSHOTs of libraries - mavenCentral() - //maven { url 'https://s01.oss.sonatype.org/content/groups/staging' } // to find libraries staged but not yet released - //maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' } // to find public SNAPSHOTs of libraries - } -} - -rootProject.name = 'Minie' - -/* - * Enumerate subdirectories in the project's root directory that contain a - * "build.gradle" file. Any subdirectory with a "build.gradle" file is - * automatically a subproject of this project. - */ -def subDirs = rootDir.listFiles( - new FileFilter() { - boolean accept(File sub) { - return sub.isDirectory() && new File(sub, 'build.gradle').isFile() - } - } -) - -subDirs.each { File sub -> - include sub.name -} +/* + * settings.gradle: global settings shared by all Minie subprojects + */ + +dependencyResolutionManagement { + repositories { + //mavenLocal() // to find local SNAPSHOTs of libraries + mavenCentral() + //maven { url 'https://s01.oss.sonatype.org/content/groups/staging' } // to find libraries staged but not yet released + //maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots' } // to find public SNAPSHOTs of libraries + } +} + +rootProject.name = 'Minie' + +/* + * Enumerate subdirectories in the project's root directory that contain a + * "build.gradle" file. Any subdirectory with a "build.gradle" file is + * automatically a subproject of this project. + */ +def subDirs = rootDir.listFiles( + new FileFilter() { + boolean accept(File sub) { + return sub.isDirectory() && new File(sub, 'build.gradle').isFile() + } + } +) + +subDirs.each { File sub -> + include sub.name +}