new repo who this
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
# Portal VR Media Viewer
|
||||
|
||||
Godot 4.6 Forward Plus / Mobile renderer project. Flat file structure -- all files in project root.
|
||||
See [README.md](README.md) for human-facing overview.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
boot.tscn (Node2D, game_loader.gd)
|
||||
|
|
||||
+-- portal.tscn (Node3D, portal.gd) <-- main scene
|
||||
|
|
||||
+-- XROrigin3D (XR tracking root)
|
||||
| +-- Perspective (XRCamera3D) <-- viewer camera
|
||||
| +-- AudioStreamPlayer (music)
|
||||
| +-- MeshInstance3D (outer tunnel, r=2.0)
|
||||
| +-- MeshInstance3D2 (mid tunnel, r=1.5)
|
||||
| +-- MeshInstance3D3 (inner tunnel, r=1.0)
|
||||
| +-- Blurrycap (sphere cap shader)
|
||||
| +-- prompt_1 / prompt_2 timers
|
||||
|
|
||||
+-- EmissionPoint (emission_point.tscn)
|
||||
|
|
||||
+-- SubViewport (shared render target)
|
||||
+-- VideoStreamPlayer (shared video)
|
||||
+-- tile.tscn instances (presentation layout)
|
||||
```
|
||||
|
||||
**Boot -> Portal -> EmissionPoint -> Tile shell** is the entry flow.
|
||||
EmissionPoint creates a configurable tile layout (hemisphere, wraparound, 9-patch, full sphere, ring of rings, spiral staircase) of tile instances, each showing the shared video via a SubViewport texture.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| [project.godot](project.godot) | Project config: Forward Plus renderer, Jolt Physics, OpenXR, mobile renderer, feature flags |
|
||||
| [game_loader.gd](game_loader.gd) | Single script: loads `portal.tscn` at startup |
|
||||
| [portal.gd](portal.gd) | Main scene controller: tunnel shader params, XR init, gamepad/mouse look, auto-centering, prompt timers, tunnel color/spiral export vars |
|
||||
| [emission_point.gd](emission_point.gd) | Core innovation: builds tile layouts from sphere face directions, shared video pool, pulse animation, format switching. Key exports: `slot_limit`, `base_radius`, `pulse_amplitude`, `pulse_speed_hz`, `hemisphere_coverage_degrees`, `sphere_rings`, `sphere_segments`, `deterministic_seed`, `presentation_format` (enum), per-format params (`wraparound_radius`, `nine_patch_radius`, `full_sphere_radius`, `ring_of_rings_*`, `spiral_staircase_*`, `transition_duration_sec`) |
|
||||
| [tile.gd](tile.gd) | Individual video tile: `place_on_shell()`, `set_global_position_unclamped()`, billboard to camera, pulse scale animation. `class_name Tile` |
|
||||
| [cloud_1.gdshader](cloud_1.gdshader) | Tunnel shader: cloud + spiral helix on CylinderMesh. Parameters: `movement_Factor`, `rotation_speed`, `scroll_speed`, `vertex_displacement`, `inner_fade`, `outer_fade`, `color`, `spiral_color`, `spiral_turns`, `spiral_swirl`, `spiral_width`, `spiral_strands`. Inputs: `noise_tex`, `cloud_tex` (NoiseTexture2D) |
|
||||
| [cap.gdshader](cap.gdshader) | Blurrycap shader: screen-space distortion with Fresnel swirl + fog. Parameters: `distortion_strength`, `swirl_strength`, `noise_amount`, `fog_density`, `tint_color`. Uses `SCREEN_UV` + `hint_screen_texture` for world-behind-sphere effect |
|
||||
| [portal.tscn](portal.tscn) | Main scene: 3 CylinderMesh (radii 2.0/1.5/1.0, each 10 tall), 3 tunnel materials, Blurrycap sphere, EmissionPoint child (base_radius=9.0, seed=0), prompt planes, timers (45s/70s), audio player |
|
||||
| [tile.tscn](tile.tscn) | Tile scene: PlaneMesh (1.6x0.9) with StandardMaterial3D, AnimationPlayer with "flying" loop (3.2s) |
|
||||
| [emission_point.tscn](emission_point.tscn) | EmissionPoint node + Timer (1.67s) |
|
||||
| [boot.tscn](boot.tscn) | Entry scene: Node2D with game_loader.gd |
|
||||
| [export_presets.cfg](export_presets.cfg) | 48 presets: 24 Windows + 24 Linux + Android variants. Each has unique `custom_features` + `include_filter` |
|
||||
| [openxr_action_map.tres](openxr_action_map.tres) | OpenXR actions for left/right hands (trigger, grip, trigger_click, trigger_touch, etc.) |
|
||||
| [animations.res](animations.res) | Animation library for prompt overlays |
|
||||
| [convert.sh](convert.sh) | ffmpeg utility: `ffmpeg -i "$1" -vf "scale=-1:420" -q:v 2 -q:a 1 -g:v 50 "$1".ogv` |
|
||||
|
||||
## Shader Reference
|
||||
|
||||
### cloud_1.gdshader (spatial, unshaded, cull_disabled)
|
||||
**Inputs**: `noise_tex` (NoiseTexture2D), `cloud_tex` (NoiseTexture2D)
|
||||
Both must be NoiseTexture2D with FastNoiseLite -- set in scene file.
|
||||
|
||||
| Parameter | Purpose |
|
||||
|---|---|
|
||||
| `movement_Factor` | How fast the cloud pattern drifts |
|
||||
| `rotation_speed` | Rotation of the tunnel texture |
|
||||
| `scroll_speed` | UV scrolling speed |
|
||||
| `vertex_displacement` | Vertex displacement intensity |
|
||||
| `inner_fade` | Fade at tunnel inner radius |
|
||||
| `outer_fade` | Fade at tunnel outer radius |
|
||||
| `color` | Base tunnel color |
|
||||
| `spiral_color` | Spiral overlay color |
|
||||
| `spiral_turns` | Number of spiral rotations |
|
||||
| `spiral_swirl` | Tightness of spiral curl |
|
||||
| `spiral_width` | Width of spiral arms |
|
||||
| `spiral_strands` | Number of spiral arms (1 or 2 for double helix) |
|
||||
|
||||
### cap.gdshader (spatial, unshaded, cull_disabled)
|
||||
**Uses**: `SCREEN_UV` + `hint_screen_texture` for world-behind-the-sphere effect.
|
||||
|
||||
| Parameter | Purpose |
|
||||
|---|---|
|
||||
| `distortion_strength` | How much the screen is warped |
|
||||
| `swirl_strength` | Swirl distortion intensity |
|
||||
| `noise_amount` | Procedural noise added to distortion |
|
||||
| `fog_density` | Fog depth for the blurred look |
|
||||
| `tint_color` | Color tint of the sphere |
|
||||
|
||||
## Code Patterns
|
||||
|
||||
### Tunnel parameter propagation
|
||||
`portal.gd` has `@export` Color/float properties for each of 3 tunnels. Setters call `_set_mesh_shader_parameter()` on `CylinderMesh.material -> ShaderMaterial`. Editor sync via `_process()` + `_enter_tree()`.
|
||||
|
||||
### Shared video pool
|
||||
EmissionPoint creates ONE SubViewport + ONE VideoStreamPlayer. All tiles share the viewport texture -- no per-tile video players.
|
||||
|
||||
### Hemisphere layout
|
||||
`_build_hemisphere_slots()` samples sphere mesh face centers (via `sphere_rings` x `sphere_segments`), filters by view direction dot product (`hemisphere_coverage_degrees`), downsamples to `slot_limit`. Uses `deterministic_seed` for reproducible layout.
|
||||
|
||||
### Tile pulse
|
||||
Sine wave in `_process()` on `mesh.scale`, synced via `phase_offset` per tile.
|
||||
|
||||
### Billboard
|
||||
Tiles `look_at(camera.global_position)` every frame. `inward_face_flip` rotates PI on Y to show the correct face.
|
||||
|
||||
### Auto-centering
|
||||
After 1.5s of no input, perspective rotates back to zero at 0.05 rad/s.
|
||||
|
||||
### Editor mode guard
|
||||
`Engine.is_editor_hint()` checks throughout `portal.gd` to prevent runtime errors in Godot editor.
|
||||
|
||||
### @tool on portal.gd
|
||||
Enables editor property sync for all tunnel shader parameters while editing.
|
||||
|
||||
## Presentation Formats
|
||||
|
||||
EmissionPoint supports 6 presentation layouts, switchable at runtime via `presentation_format` enum (`@export`). Tiles are **never** created or destroyed on format switch — they are repositioned via a smooth two-phase transition (position lerp + scale fade) over `transition_duration_sec` seconds.
|
||||
|
||||
### Available Formats
|
||||
|
||||
| Format | Layout | Animation |
|
||||
|---|---|---|
|
||||
| `HEMISPHERE` | Hemisphere of directions facing camera (existing) | Sinusoidal radius pulse |
|
||||
| `WRAPAROUND` | Horizontal circle at camera height around viewer | Circle rotates at `wraparound_rotation_speed_rad_s` |
|
||||
| `NINE_PATCH` | Fixed 3x3 grid in camera-local space | Per-tile scale wobble via `nine_patch_wobble_*` params |
|
||||
| `FULL_SPHERE` | Uniform sphere via Fibonacci golden-angle distribution | Static positions, only pulse (no rotation) |
|
||||
| `RING_OF_RINGS` | 3 concentric rings (N/4 inner, N/2 middle, N/4 outer) | Each ring rotates at independent speed → parallax |
|
||||
| `SPIRAL_STAIRCASE` | Rising helix at fixed radius, climbing over `spiral_staircase_turns` rotations | Slow spiral rotation + per-tile vertical drift; partial billboard |
|
||||
|
||||
### Per-Format Parameters
|
||||
|
||||
**Wraparound**: `wraparound_radius`, `wraparound_rotation_speed_rad_s`
|
||||
|
||||
**Nine-Patch**: `nine_patch_radius`, `nine_patch_wobble_speed_hz`, `nine_patch_wobble_amplitude`, `nine_patch_phase_spread`
|
||||
|
||||
**Full Sphere**: `full_sphere_radius`, `full_sphere_use_fibonacci`
|
||||
|
||||
**Ring of Rings**: `ring_of_rings_inner_radius`, `ring_of_rings_middle_radius`, `ring_of_rings_outer_radius`, `ring_of_rings_inner_speed`, `ring_of_rings_middle_speed`, `ring_of_rings_outer_speed`, `ring_of_rings_vertical_wobble`
|
||||
|
||||
**Spiral Staircase**: `spiral_staircase_radius`, `spiral_staircase_height`, `spiral_staircase_turns`, `spiral_staircase_rotation_speed_rad_s`, `spiral_staircase_wobble_amplitude`
|
||||
|
||||
**Common**: `transition_duration_sec` — shared transition duration for all format switches
|
||||
|
||||
### Transition System
|
||||
|
||||
The transition uses `_handle_transition(time_sec)` in `emission_point.gd`:
|
||||
1. **Phase 1 (0 → 0.5)**: Positions lerp from start → target via `smoothstep()`
|
||||
2. **Phase 2 (0.5 → 1.0)**: Scale multiplier fades from 0 → 1 (tiles pop in)
|
||||
3. On completion: `_current_format = _target_format`, `_format_transition_progress = 1.0`
|
||||
|
||||
Extra slots in smaller layouts (e.g., 9-patch with 60 tile capacity) are placed at `Vector3.INF` and become visible during the fade-in.
|
||||
|
||||
### Key Math
|
||||
|
||||
- **Wraparound**: `angle = (2π*i/N) + accumulator` on Y=0 plane
|
||||
- **Nine-Patch**: `center + cam_right*(col-1)*spacing + cam_up*(row-1)*spacing - cam_forward*radius`
|
||||
- **Full Sphere**: Fibonacci golden angle `γ = π(3-√5)`, `y = 1 - (2i+1)/N`
|
||||
- **Ring of Rings**: N/4 inner ring, N/2 middle ring, rest outer — each with independent rotation
|
||||
- **Spiral Staircase**: `angle = (i/N)*turns*τ + accumulator`, `height = (i/N - 0.5)*height`
|
||||
|
||||
### Setting via Export Preset
|
||||
|
||||
Presentation format can be governed externally by setting the `presentation_format` export on the EmissionPoint node. In timeline/content editor workflows, this parameter is bindable via Godot's property binding system — a server or external controller can call `emission_point.set_presentation_format(EmissionPoint.PresentationFormat.NINE_PATCH)` at any time.
|
||||
|
||||
## Export Preset System
|
||||
|
||||
Content is controlled entirely through `export_presets.cfg`, not separate scene files. Each preset:
|
||||
|
||||
- **`custom_features`**: comma-separated flags (e.g., `smoke,poppers,stoner_dad_3`). Used at runtime for conditional logic.
|
||||
- **`include_filter`**: glob for video files to bundle (e.g., `*chubs*.ogv`, `stoner_dad_3.ogv`).
|
||||
- **`export_files`**: which scene files to include (always `boot.tscn`, `tile.tscn`, `emission_point.tscn`; main portal varies).
|
||||
- **`export_path`**: output binary path in `bin/`.
|
||||
|
||||
### Content variants
|
||||
| Variant | Video Pattern |
|
||||
|---|---|
|
||||
| stoner_dad_2 | `*stoner_dad_2*.ogv` |
|
||||
| stoner_dad_3 | `*stoner_dad_3*.ogv` |
|
||||
| stoner_plastic | `*stoner_plastic*.ogv` |
|
||||
| chubs_compilation | `*chubs*.ogv` |
|
||||
| 420_special | `*420_special*.ogv` |
|
||||
| 420_special_diet | `*420_special_diet*.ogv` |
|
||||
| orc_domination | `*orc*.ogv` |
|
||||
| furry_poppers_daddy | `*poppers*.ogv` |
|
||||
| furry_poppers_stable_stud | `*poppers*.ogv` |
|
||||
| toon_goon | `*toon*.ogv` |
|
||||
| furry_goon_encouragement | `*furry_goon*.ogv` |
|
||||
| furry_big_bad_wolf | `*wolf*.ogv` |
|
||||
|
||||
### Platform outputs
|
||||
- Windows: `bin/windows/portal_<name>.exe`
|
||||
- Linux: `bin/linux/portal_<name>.x86_64`
|
||||
- Android (Meta Quest): `bin/android/portal_<name>.apk`
|
||||
|
||||
## Common Modifications
|
||||
|
||||
**Change tunnel colors/spirals**: Edit `portal.tscn` -> Node3D node properties or `portal.gd` defaults.
|
||||
|
||||
**Change tile count/coverage**: Edit `emission_point.tscn` -> EmissionPoint node -> `slot_limit`, `hemisphere_coverage_degrees`, `sphere_rings`, `sphere_segments`.
|
||||
|
||||
**Add new content variant**: Add export preset in Godot (Export -> Presets), set `custom_features` + `include_filter` + `export_files` + `export_path`.
|
||||
|
||||
**Add prompt overlay**: Add MeshInstance3D + Animation to `portal.tscn`, connect timer -> AnimationPlayer.play().
|
||||
|
||||
**Change video**: Place `.ogv` in project root (use [convert.sh](convert.sh)), set `video_stream` export on EmissionPoint, update `include_filter` in export preset.
|
||||
|
||||
## Important Notes
|
||||
|
||||
- No subdirectories for source code. Everything is flat in project root.
|
||||
- Audio files are `.mp3` (not ogv). Music player uses `AudioStreamMP3`.
|
||||
- Video content uses `.ogv` format, not bundled in git (filtered by `include_filter`).
|
||||
- Clear color is transparent (`Color(0,0,0,0)`) -- not black.
|
||||
- `animations.res` contains animation libraries for prompt overlays.
|
||||
- Boot scene hardcodes main scene as `"portal.tscn"` string rather than UID -- a minor maintenance fragility.
|
||||
- Hand tracking enabled by default on Meta Quest (via custom Android manifest).
|
||||
Reference in New Issue
Block a user