From 8410ef089fcaf96319ce5e2520926c14617865ac Mon Sep 17 00:00:00 2001 From: Daniel Abeshouse Date: Mon, 23 Feb 2026 16:31:54 -0500 Subject: [PATCH 1/3] Null check every access of a Scene's Program --- .../ik/poser/scene/AbstractPoserScene.java | 9 +++++++-- .../scene/PoserPicturePlaneInteraction.java | 19 +++++++++++-------- .../story/implementation/JointedModelImp.java | 6 +++++- .../eventhandling/EventManager.java | 15 +++++++++------ .../eventhandling/IsInViewDetector.java | 11 +++++++---- .../eventhandling/TimerEventHandler.java | 7 ++++++- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/core/ide/src/main/java/org/lgna/ik/poser/scene/AbstractPoserScene.java b/core/ide/src/main/java/org/lgna/ik/poser/scene/AbstractPoserScene.java index 9a0388b6b1..5d506aee6a 100644 --- a/core/ide/src/main/java/org/lgna/ik/poser/scene/AbstractPoserScene.java +++ b/core/ide/src/main/java/org/lgna/ik/poser/scene/AbstractPoserScene.java @@ -66,6 +66,7 @@ import org.lgna.story.SpatialRelation; import org.lgna.story.TurnDirection; import org.lgna.story.implementation.JointImp; +import org.lgna.story.implementation.ProgramImp; import org.lgna.story.resources.JointId; import java.awt.event.MouseEvent; @@ -180,7 +181,8 @@ private void performInitializeEvents() { } private OnscreenRenderTarget getOnscreenRenderTarget() { - return getImplementation().getProgram().getOnscreenRenderTarget(); + ProgramImp program = getImplementation().getProgram(); + return program == null ? null : program.getOnscreenRenderTarget(); } public void jointSelected(JointSelectionSphere sphere, MouseEvent e) { @@ -195,7 +197,10 @@ public void jointSelected(JointSelectionSphere sphere, MouseEvent e) { public void addCustomDragAdapter() { synchronized (dragListeners) { poserAnimatorDragAdapter = new PoserAnimatorDragAdapter(this); - poserAnimatorDragAdapter.setAnimator(getImplementation().getProgram().getAnimator()); + ProgramImp program = getImplementation().getProgram(); + if (program != null) { + poserAnimatorDragAdapter.setAnimator(program.getAnimator()); + } poserAnimatorDragAdapter.setInteractionState(HandleStyle.ROTATION); poserAnimatorDragAdapter.setTarget(model); poserAnimatorDragAdapter.setOnscreenRenderTarget(getOnscreenRenderTarget()); diff --git a/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java b/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java index 66a37f5ff3..23f64f4891 100644 --- a/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java +++ b/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java @@ -61,6 +61,7 @@ import org.lgna.story.SSphere; import org.lgna.story.implementation.CameraImp; import org.lgna.story.implementation.EntityImp; +import org.lgna.story.implementation.ProgramImp; import org.lgna.story.implementation.SceneImp; import javax.swing.SwingUtilities; @@ -244,14 +245,16 @@ private JointSelectionSphere pickJoint(JointSelectionSphere one, JointSelectionS } private ManipulationHandle3D checkIfHandleSelected(MouseEvent e) { - SceneImp implementation = scene.getImplementation(); - RenderTarget rt = implementation.getProgram().getOnscreenRenderTarget(); - PickResult pickResult = rt.getSynchronousPicker().pickFrontMost(e.getPoint(), PickSubElementPolicy.NOT_REQUIRED); - if ((pickResult != null) && (pickResult.getVisual() != null)) { - Composite composite = pickResult.getVisual().getParent(); - if (composite != null) { - if (composite.getParent() instanceof ManipulationHandle3D) { - return (ManipulationHandle3D) composite.getParent(); + ProgramImp program = scene.getImplementation().getProgram(); + if (program != null) { + RenderTarget rt = program.getOnscreenRenderTarget(); + PickResult pickResult = rt.getSynchronousPicker().pickFrontMost(e.getPoint(), PickSubElementPolicy.NOT_REQUIRED); + if ((pickResult != null) && (pickResult.getVisual() != null)) { + Composite composite = pickResult.getVisual().getParent(); + if (composite != null) { + if (composite.getParent() instanceof ManipulationHandle3D) { + return (ManipulationHandle3D) composite.getParent(); + } } } } diff --git a/core/story-api/src/main/java/org/lgna/story/implementation/JointedModelImp.java b/core/story-api/src/main/java/org/lgna/story/implementation/JointedModelImp.java index 3d79478dcf..504da15115 100644 --- a/core/story-api/src/main/java/org/lgna/story/implementation/JointedModelImp.java +++ b/core/story-api/src/main/java/org/lgna/story/implementation/JointedModelImp.java @@ -938,7 +938,11 @@ public Animated getAnimated() { } public void strikePose(Pose pose, double duration, Style style) { - this.getProgram().perform(new PoseAnimation(duration, style, this, pose), null); + ProgramImp program = getProgram(); + if (program == null) { + return; + } + program.perform(new PoseAnimation(duration, style, this, pose), null); } private final A abstraction; diff --git a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java index 6e6865183c..097c815e7a 100644 --- a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java +++ b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java @@ -56,6 +56,7 @@ import org.lgna.story.SThing; import org.lgna.story.Visual; import org.lgna.story.event.*; +import org.lgna.story.implementation.ProgramImp; import org.lgna.story.implementation.SceneImp; import java.awt.*; @@ -237,12 +238,14 @@ public void sceneActivated() { public void addDragAdapter(Visual[] visuals) { if (this.dragAdapter == null) { this.dragAdapter = new RuntimeDragAdapter(visuals); - OnscreenRenderTarget renderTarget = this.scene.getProgram().getOnscreenRenderTarget(); - SymmetricPerspectiveCamera camera = (SymmetricPerspectiveCamera) scene.findFirstCamera().getSgCamera(); - this.dragAdapter.setOnscreenRenderTarget(renderTarget); - this.dragAdapter.addCameraView(CameraView.MAIN, camera); - this.dragAdapter.makeCameraActive(camera); - this.dragAdapter.setAnimator(this.scene.getProgram().getAnimator()); + ProgramImp program = scene.getProgram(); + if (program != null) { + SymmetricPerspectiveCamera camera = (SymmetricPerspectiveCamera) scene.findFirstCamera().getSgCamera(); + this.dragAdapter.setOnscreenRenderTarget(program.getOnscreenRenderTarget()); + this.dragAdapter.addCameraView(CameraView.MAIN, camera); + this.dragAdapter.makeCameraActive(camera); + this.dragAdapter.setAnimator(program.getAnimator()); + } } else { dragAdapter.addTargets(visuals); } diff --git a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/IsInViewDetector.java b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/IsInViewDetector.java index 20fc29ab40..47bcde4d0c 100644 --- a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/IsInViewDetector.java +++ b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/IsInViewDetector.java @@ -48,6 +48,7 @@ import org.lgna.story.SThing; import org.lgna.story.implementation.CameraImp; import org.lgna.story.implementation.EntityImp; +import org.lgna.story.implementation.ProgramImp; import java.awt.Dimension; import java.awt.Point; @@ -65,12 +66,14 @@ public static boolean isThisInView(SThing entity, CameraImp camera) { for (int i = 0; i < points.length; ++i) { awtPoints[i] = implementation.transformToAwt(points[i], camera); } - camera.getScene().getProgram().getOnscreenRenderTarget(); - return isInView(camera, awtPoints, relativeToCamera); + ProgramImp program = camera.getScene().getProgram(); + if (program == null) { + return false; + } + return isInView(program.getOnscreenRenderTarget().getSurfaceSize(), awtPoints, relativeToCamera); } - private static boolean isInView(CameraImp camera, Point[] awtPoints, Point3[] relativeToCamera) { - Dimension surfaceSize = camera.getScene().getProgram().getOnscreenRenderTarget().getSurfaceSize(); + private static boolean isInView(Dimension surfaceSize, Point[] awtPoints, Point3[] relativeToCamera) { boolean leftOf = false; boolean rightOf = false; boolean above = false; diff --git a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/TimerEventHandler.java b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/TimerEventHandler.java index 420a961eea..ffc7873813 100644 --- a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/TimerEventHandler.java +++ b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/TimerEventHandler.java @@ -49,6 +49,7 @@ import edu.cmu.cs.dennisc.render.gl.GlrRenderFactory; import org.lgna.story.MultipleEventPolicy; import org.lgna.story.event.*; +import org.lgna.story.implementation.ProgramImp; import java.util.List; import java.util.Map; @@ -69,7 +70,11 @@ public class TimerEventHandler extends AbstractEventHandler Date: Mon, 23 Feb 2026 20:30:49 -0500 Subject: [PATCH 2/3] Add null Program checking in SceneImp. Too much code cleanup. --- .../main/java/org/lgna/story/SProgram.java | 2 +- .../lgna/story/implementation/SceneImp.java | 120 +++++++----------- 2 files changed, 49 insertions(+), 73 deletions(-) diff --git a/core/story-api/src/main/java/org/lgna/story/SProgram.java b/core/story-api/src/main/java/org/lgna/story/SProgram.java index 70eccf0153..e5f4e364a5 100644 --- a/core/story-api/src/main/java/org/lgna/story/SProgram.java +++ b/core/story-api/src/main/java/org/lgna/story/SProgram.java @@ -72,7 +72,7 @@ public SScene getActiveScene() { public void setActiveScene(SScene scene) { if (this.activeScene != null) { - this.activeScene.getImplementation().deactivate(this.getImplementation()); + this.activeScene.getImplementation().deactivate(); } this.activeScene = scene; if (this.activeScene != null) { diff --git a/core/story-api/src/main/java/org/lgna/story/implementation/SceneImp.java b/core/story-api/src/main/java/org/lgna/story/implementation/SceneImp.java index 3fa28dd3e9..76c54fb445 100644 --- a/core/story-api/src/main/java/org/lgna/story/implementation/SceneImp.java +++ b/core/story-api/src/main/java/org/lgna/story/implementation/SceneImp.java @@ -70,7 +70,7 @@ * @author Dennis Cosgrove */ public class SceneImp extends EntityImp { - private static final Transformable createDirectionalLightTransformable(DirectionalLight sgDirectionalLight, Angle yaw, Angle pitch, float brightness) { + private static Transformable createDirectionalLightTransformable(DirectionalLight sgDirectionalLight, Angle yaw, Angle pitch, float brightness) { Transformable rv = new Transformable(); rv.applyRotationAboutYAxis(yaw); rv.applyRotationAboutXAxis(pitch); @@ -128,24 +128,17 @@ private void fireSceneActivationListeners() { this.eventManager.sceneActivated(); } - private void changeActiveStatus(ProgramImp programImp, boolean isActive, int activationCount) { - double prevSimulationSpeedFactor = program.getSimulationSpeedFactor(); - program.setSimulationSpeedFactor(Double.POSITIVE_INFINITY); + private void changeActiveStatus(boolean isActive) { + double prevSimulationSpeedFactor = 0; + if (program != null) { + prevSimulationSpeedFactor = program.getSimulationSpeedFactor(); + program.setSimulationSpeedFactor(Double.POSITIVE_INFINITY); + } if (ACCEPTABLE_HACK_FOR_SCENE_EDITOR_performMinimalInitializationCount <= 0) { - this.getAbstraction().handleActiveChanged(isActive, activationCount); + this.getAbstraction().handleActiveChanged(isActive, activeCount); } - program.setSimulationSpeedFactor(prevSimulationSpeedFactor); - if (isActive) { - //This forces the scene to initialize itself to make sure we can properly query bounding boxes and other render dependent things - //All this info is critical to a scene running - AdapterFactory.getAdapterFor(this.sgScene); - - this.addCamerasTo(programImp); - if (ACCEPTABLE_HACK_FOR_SCENE_EDITOR_performMinimalInitializationCount <= 0) { - this.fireSceneActivationListeners(); - } - } else { - this.removeCamerasFrom(programImp); + if (program != null) { + program.setSimulationSpeedFactor(prevSimulationSpeedFactor); } } @@ -156,20 +149,49 @@ public void activate(ProgramImp programImp) { if (this.isGlobalLightBrightnessAnimationDesired) { this.setGlobalBrightness(0.0f); } - this.changeActiveStatus(program, true, activeCount); + this.changeActiveStatus(true); + initializeScene(); if (this.isGlobalLightBrightnessAnimationDesired) { this.animateGlobalBrightness(1.0f, 0.5, TraditionalStyle.BEGIN_AND_END_GENTLY); } } - public void deactivate(ProgramImp programImp) { + private void setProgram(ProgramImp newImp) { + if (newImp.equals(this.program)) { + //no change + return; + } + if (program != null) { + eventManager.removeListenersFrom(program.getOnscreenRenderTarget()); + } + this.program = newImp; + eventManager.addListenersTo(program.getOnscreenRenderTarget()); + } + + private void initializeScene() { + //This forces the scene to initialize itself to make sure we can properly query bounding boxes and other render dependent things + //All this info is critical to a scene running + AdapterFactory.getAdapterFor(this.sgScene); + + this.addCamerasTo(program); + if (ACCEPTABLE_HACK_FOR_SCENE_EDITOR_performMinimalInitializationCount <= 0) { + this.fireSceneActivationListeners(); + } + } + + public void deactivate() { deactiveCount++; assert deactiveCount == activeCount; if (this.isGlobalLightBrightnessAnimationDesired) { this.animateGlobalBrightness(0.0f, 0.25, TraditionalStyle.BEGIN_AND_END_GENTLY); } - this.changeActiveStatus(programImp, false, activeCount); - this.setProgram(null); + this.changeActiveStatus(false); + if (program == null) { + return; + } + this.removeCamerasFrom(program); + eventManager.removeListenersFrom(program.getOnscreenRenderTarget()); + program = null; } @Override @@ -192,41 +214,6 @@ public ProgramImp getProgram() { return this.program; } - public void setProgram(ProgramImp program) { - if (this.program != program) { - if (this.program != null) { - this.eventManager.removeListenersFrom(this.program.getOnscreenRenderTarget()); - } - this.program = program; - if (program != null) { - this.eventManager.addListenersTo(program.getOnscreenRenderTarget()); - } - } - this.program = program; - } - - //todo - // private static class Capsule { - // private final TransformableImp transformable; - // private EntityImp vehicle; - // private edu.cmu.cs.dennisc.math.AffineMatrix4x4 localTransformation; - // - // public Capsule( TransformableImp transformable ) { - // this.transformable = transformable; - // } - // - // public void preserve() { - // this.vehicle = this.transformable.getVehicle(); - // this.localTransformation = this.transformable.getSgComposite().getLocalTransformation(); - // } - // - // public void restore() { - // this.transformable.setVehicle( this.vehicle ); - // this.transformable.getSgComposite().setLocalTransformation( this.localTransformation ); - // } - // } - // private final java.util.List capsules = edu.cmu.cs.dennisc.java.util.Lists.newCopyOnWriteArrayList(); - public void preserveStateAndEventListeners() { this.eventManager.silenceAllListeners(); //todo: preserve state @@ -237,7 +224,7 @@ public void restoreStateAndEventListeners() { this.eventManager.restoreAllListeners(); } - public void addCamerasTo(ProgramImp program) { + private void addCamerasTo(ProgramImp program) { for (AbstractCamera sgCamera : VisitUtilities.getAll(this.sgScene, AbstractCamera.class)) { EntityImp entityImp = EntityImp.getInstance(sgCamera); if (entityImp instanceof CameraImp cameraImp) { @@ -246,7 +233,7 @@ public void addCamerasTo(ProgramImp program) { } } - public void removeCamerasFrom(ProgramImp program) { + private void removeCamerasFrom(ProgramImp program) { for (AbstractCamera sgCamera : VisitUtilities.getAll(this.sgScene, AbstractCamera.class)) { EntityImp entityImp = EntityImp.getInstance(sgCamera); if (entityImp instanceof CameraImp cameraImp) { @@ -260,11 +247,11 @@ public CameraImp findFirstCamera() { return (CameraImp) EntityImp.getInstance(sgCamera); } - public void setGlobalBrightness(float globalBrightness) { + private void setGlobalBrightness(float globalBrightness) { this.sgScene.globalBrightness.setValue(globalBrightness); } - public void animateGlobalBrightness(float globalBrightness, double duration, Style style) { + private void animateGlobalBrightness(float globalBrightness, double duration, Style style) { duration = adjustDurationIfNecessary(duration); if (EpsilonUtilities.isWithinReasonableEpsilon(duration, RIGHT_NOW)) { this.setGlobalBrightness(globalBrightness); @@ -296,7 +283,7 @@ public EventManager getEventManager() { private int activeCount; private int deactiveCount; - private boolean isGlobalLightBrightnessAnimationDesired = true; + private final boolean isGlobalLightBrightnessAnimationDesired = true; private final Scene sgScene = new Scene(); private final Background sgBackground = new Background(); @@ -346,17 +333,6 @@ protected void handleSetValue(Color value) { SceneImp.this.sgFromBelowDirectionalLight.color.setValue(value.toColor4f()); } }; - public final FloatProperty globalLightBrightness = new FloatProperty(SceneImp.this) { - @Override - public Float getValue() { - return SceneImp.this.sgScene.globalBrightness.getValue(); - } - - @Override - protected void handleSetValue(Float value) { - SceneImp.this.sgScene.globalBrightness.setValue(value); - } - }; private static class FogDensityProperty extends FloatProperty { public FogDensityProperty(SceneImp owner) { From 44412f166fa677e625f32c49050de6ab0feb4b51 Mon Sep 17 00:00:00 2001 From: Daniel Abeshouse Date: Tue, 24 Feb 2026 13:28:37 -0500 Subject: [PATCH 3/3] Outdent code per PR comment --- .../scene/PoserPicturePlaneInteraction.java | 22 +++++++++---------- .../eventhandling/EventManager.java | 13 ++++++----- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java b/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java index 23f64f4891..9891a99b14 100644 --- a/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java +++ b/core/ide/src/main/java/org/lgna/ik/poser/scene/PoserPicturePlaneInteraction.java @@ -246,17 +246,17 @@ private JointSelectionSphere pickJoint(JointSelectionSphere one, JointSelectionS private ManipulationHandle3D checkIfHandleSelected(MouseEvent e) { ProgramImp program = scene.getImplementation().getProgram(); - if (program != null) { - RenderTarget rt = program.getOnscreenRenderTarget(); - PickResult pickResult = rt.getSynchronousPicker().pickFrontMost(e.getPoint(), PickSubElementPolicy.NOT_REQUIRED); - if ((pickResult != null) && (pickResult.getVisual() != null)) { - Composite composite = pickResult.getVisual().getParent(); - if (composite != null) { - if (composite.getParent() instanceof ManipulationHandle3D) { - return (ManipulationHandle3D) composite.getParent(); - } - } - } + if (program == null) { + return null; + } + RenderTarget rt = program.getOnscreenRenderTarget(); + PickResult pickResult = rt.getSynchronousPicker().pickFrontMost(e.getPoint(), PickSubElementPolicy.NOT_REQUIRED); + if ((pickResult == null) || (pickResult.getVisual() == null)) { + return null; + } + Composite composite = pickResult.getVisual().getParent(); + if (composite != null && composite.getParent() instanceof ManipulationHandle3D) { + return (ManipulationHandle3D) composite.getParent(); } return null; } diff --git a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java index 097c815e7a..a91d041b39 100644 --- a/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java +++ b/core/story-api/src/main/java/org/lgna/story/implementation/eventhandling/EventManager.java @@ -239,13 +239,14 @@ public void addDragAdapter(Visual[] visuals) { if (this.dragAdapter == null) { this.dragAdapter = new RuntimeDragAdapter(visuals); ProgramImp program = scene.getProgram(); - if (program != null) { - SymmetricPerspectiveCamera camera = (SymmetricPerspectiveCamera) scene.findFirstCamera().getSgCamera(); - this.dragAdapter.setOnscreenRenderTarget(program.getOnscreenRenderTarget()); - this.dragAdapter.addCameraView(CameraView.MAIN, camera); - this.dragAdapter.makeCameraActive(camera); - this.dragAdapter.setAnimator(program.getAnimator()); + if (program == null) { + return; } + SymmetricPerspectiveCamera camera = (SymmetricPerspectiveCamera) scene.findFirstCamera().getSgCamera(); + this.dragAdapter.setOnscreenRenderTarget(program.getOnscreenRenderTarget()); + this.dragAdapter.addCameraView(CameraView.MAIN, camera); + this.dragAdapter.makeCameraActive(camera); + this.dragAdapter.setAnimator(program.getAnimator()); } else { dragAdapter.addTargets(visuals); }