Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 4 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions red_green_playground.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
# Flask app initialization
app = Flask(__name__, static_folder=os.path.join(build_path, "static"))


@app.after_request
def add_cors_headers(response):
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
return response

def get_anim(frames, framerate=30, skip_t = 1):
"""
frames: list of N np.arrays (H x W x 3)
Expand Down
94 changes: 85 additions & 9 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import VideoPlayer from "./components/VideoPlayer";
import NavigationBar from "./components/playground/NavigationBar";
import SimulationSettingsPanel from "./components/playground/SimulationSettingsPanel";
import SceneControlsPanel from "./components/playground/SceneControlsPanel";
import SelectedEntityPanel from "./components/playground/SelectedEntityPanel";
import TrajectoryScrubPanel from "./components/playground/TrajectoryScrubPanel";
import OcclusionPresetsPanel from "./components/playground/OcclusionPresetsPanel";
import DistractorControlsPanel from "./components/playground/DistractorControlsPanel";
Expand All @@ -21,6 +22,7 @@ import { DEFAULT_RANDOM_DISTRACTOR_PARAMS, VID_RES, PX_SCALE, INTERVAL, BORDER_P

function App() {
const videoPlayerRef = useRef(null);
const handleSimulateRef = useRef(null);

// Simulation parameters
const [videoLength, setVideoLength] = useState(10);
Expand Down Expand Up @@ -52,6 +54,7 @@ function App() {
// Trajectory scrub state
const [scrubEnabled, setScrubEnabled] = useState(false);
const [scrubFrame, setScrubFrame] = useState(0);
const [selectedEntityId, setSelectedEntityId] = useState(null);

// Use hooks for state management
const entitiesHook = useEntities(worldWidth, worldHeight);
Expand All @@ -68,6 +71,7 @@ function App() {

const sceneTransformHook = useSceneTransform(entities, setEntities, worldWidth, worldHeight, movementUnit, setTargetDirection, setDirectionInput);
const { moveScene, rotateScene } = sceneTransformHook;
const selectedEntity = entities.find((entity) => entity.id === selectedEntityId) || null;

// Keyboard event listener for arrow keys
useEffect(() => {
Expand All @@ -79,12 +83,27 @@ function App() {
e.preventDefault();
moveScene(e.key);
}
if ((e.key === 'Delete' || e.key === 'Backspace') && selectedEntityId !== null) {
e.preventDefault();
deleteEntity(selectedEntityId);
setSelectedEntityId(null);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [moveScene]);
}, [moveScene, selectedEntityId, deleteEntity]);

useEffect(() => {
if (selectedEntityId === null) {
return;
}
const selectedStillExists = entities.some((entity) => entity.id === selectedEntityId);
if (!selectedStillExists) {
setSelectedEntityId(null);
}
}, [entities, selectedEntityId]);

// Derive effective occluders after applying windows, and validate overlaps against that.
const { entitiesForSimulation, occluderPieces } = getEntitiesWithWindowsApplied(entities);
Expand Down Expand Up @@ -112,6 +131,7 @@ function App() {
// Wrapper for clearAllEntities to also clear simData and distractor data
const handleClearAll = () => {
clearAllEntities();
setSelectedEntityId(null);
setSimData(null);
resetDistractorParams();
};
Expand Down Expand Up @@ -141,6 +161,30 @@ function App() {
setScrubFrame(0);
handleSimulateBase(entitiesForSimulation, simulationParams, mode, keyDistractors, randomDistractorParams, autoRun);
};
handleSimulateRef.current = handleSimulate;

useEffect(() => {
const onKeyDown = (e) => {
const isEnterKey =
e.key === "Enter" ||
e.key === "Return" ||
e.code === "Enter" ||
e.code === "NumpadEnter";
const hasShortcutModifier = e.metaKey || e.ctrlKey;

if (!isEnterKey || !hasShortcutModifier || e.isComposing) {
return;
}
if (!(isValidPhysics && overlapValidation.valid)) {
return;
}
e.preventDefault();
handleSimulateRef.current?.(false);
};
// Capture phase makes shortcut more reliable when focused controls stop bubbling.
window.addEventListener("keydown", onKeyDown, true);
return () => window.removeEventListener("keydown", onKeyDown, true);
}, [isValidPhysics, overlapValidation.valid]);

// File operation handlers
const handleFileLoad = createFileLoadHandler({
Expand Down Expand Up @@ -206,10 +250,6 @@ function App() {
}
};

const handleEntityDrag = (entity, d) => {
updateEntityFromDrag(entity, d);
};

const handleEntityDragStop = (entity, d) => {
updateEntityFromDrag(entity, d);
};
Expand Down Expand Up @@ -241,10 +281,6 @@ function App() {
updateEntity(entity.id, updatedEntity);
};

const handleEntityResize = (entity, ref, position) => {
updateEntityFromResize(entity, ref, position);
};

const handleEntityResizeStop = (entity, ref, position) => {
updateEntityFromResize(entity, ref, position);
};
Expand Down Expand Up @@ -332,10 +368,42 @@ function App() {

const handleCanvasClick = () => {
setContextMenu({ visible: false, x: 0, y: 0, entityId: null });
setSelectedEntityId(null);
};

const handleDeleteEntity = (id) => {
deleteEntity(id);
if (id === selectedEntityId) {
setSelectedEntityId(null);
}
};

const handleSelectedEntityFieldChange = (field, value) => {
if (!selectedEntity) return;
const numericValue = Number(value);
if (Number.isNaN(numericValue)) return;

if (field === "directionDegrees" && selectedEntity.type === "target") {
handleUpdateTargetDirection(numericValue);
return;
}

const nextEntity = { ...selectedEntity };

if (field === "width" && selectedEntity.type !== "target") {
nextEntity.width = Math.max(INTERVAL, numericValue);
} else if (field === "height" && selectedEntity.type !== "target") {
nextEntity.height = Math.max(INTERVAL, numericValue);
} else if (field === "x" || field === "y") {
nextEntity[field] = numericValue;
} else {
return;
}

nextEntity.x = Math.max(0, Math.min(nextEntity.x, worldWidth - nextEntity.width));
nextEntity.y = Math.max(0, Math.min(nextEntity.y, worldHeight - nextEntity.height));

updateEntity(selectedEntity.id, nextEntity);
};

const handleUpdateTargetDirection = (angleDegrees) => {
Expand Down Expand Up @@ -428,6 +496,12 @@ function App() {
hasEntities={entities.length > 0}
/>

<SelectedEntityPanel
selectedEntity={selectedEntity}
onChangeField={handleSelectedEntityFieldChange}
onDeleteEntity={handleDeleteEntity}
/>

<TrajectoryScrubPanel
enabled={scrubEnabled}
onToggleEnabled={handleToggleScrubEnabled}
Expand Down Expand Up @@ -484,6 +558,8 @@ function App() {
onUpdateTargetDirection={handleUpdateTargetDirection}
updateEntity={updateEntity}
overlapRegions={overlapValidation.overlapRegions}
selectedEntityId={selectedEntityId}
onEntitySelect={setSelectedEntityId}
/>

{/* Video Player Section */}
Expand Down
Loading