summer-engine 4.3.0 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -1
- package/.codex-plugin/plugin.json +5 -3
- package/.cursor-plugin/plugin.json +4 -2
- package/.factory-plugin/plugin.json +4 -2
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +100 -0
- package/README.md +67 -27
- package/commands/start.md +19 -15
- package/commands/summer.md +1 -0
- package/dist/cli/command-usage.d.ts +3 -1
- package/dist/cli/command-usage.js +10 -4
- package/dist/cli/commands/create.d.ts +1 -25
- package/dist/cli/commands/create.js +10 -138
- package/dist/cli/commands/dev.d.ts +66 -0
- package/dist/cli/commands/dev.js +234 -0
- package/dist/cli/commands/install.d.ts +15 -58
- package/dist/cli/commands/install.js +31 -481
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/ops.d.ts +17 -1
- package/dist/cli/commands/ops.js +97 -9
- package/dist/cli/commands/preview.d.ts +58 -0
- package/dist/cli/commands/preview.js +181 -0
- package/dist/cli/commands/run.d.ts +1 -17
- package/dist/cli/commands/run.js +17 -206
- package/dist/cli/commands/setup.js +175 -32
- package/dist/cli/index.js +5 -0
- package/dist/core/agent-host.d.ts +3 -0
- package/dist/core/agent-host.js +1 -1
- package/dist/core/api-client.d.ts +10 -2
- package/dist/core/api-client.js +13 -8
- package/dist/core/capabilities/agent-playbook.js +1 -0
- package/dist/core/capabilities/apps.d.ts +9 -9
- package/dist/core/capabilities/asset-import.d.ts +4 -1
- package/dist/core/capabilities/asset-import.js +15 -6
- package/dist/core/capabilities/capture-gameplay.js +1 -1
- package/dist/core/capabilities/connect-signal.d.ts +2 -2
- package/dist/core/capabilities/content-make/index.d.ts +81 -0
- package/dist/core/capabilities/content-make/index.js +706 -0
- package/dist/core/capabilities/content-make/media.d.ts +57 -0
- package/dist/core/capabilities/content-make/media.js +172 -0
- package/dist/core/capabilities/content-make/plan.d.ts +130 -0
- package/dist/core/capabilities/content-make/plan.js +433 -0
- package/dist/core/capabilities/content-make/render.d.ts +64 -0
- package/dist/core/capabilities/content-make/render.js +370 -0
- package/dist/core/capabilities/content-make/study.d.ts +13 -0
- package/dist/core/capabilities/content-make/study.js +165 -0
- package/dist/core/capabilities/content-make/types.d.ts +173 -0
- package/dist/core/capabilities/content-make/types.js +6 -0
- package/dist/core/capabilities/content-make/voice.d.ts +7 -0
- package/dist/core/capabilities/content-make/voice.js +27 -0
- package/dist/core/capabilities/events.d.ts +2 -2
- package/dist/core/capabilities/export-game.d.ts +4 -1
- package/dist/core/capabilities/export-game.js +4 -3
- package/dist/core/capabilities/export-presets.d.ts +2 -1
- package/dist/core/capabilities/export-presets.js +2 -1
- package/dist/core/capabilities/go-live.d.ts +14 -0
- package/dist/core/capabilities/go-live.js +408 -0
- package/dist/core/capabilities/image-generation.d.ts +6 -6
- package/dist/core/capabilities/item-manifest.d.ts +92 -0
- package/dist/core/capabilities/item-manifest.js +247 -0
- package/dist/core/capabilities/placement.d.ts +7 -7
- package/dist/core/capabilities/projects-local.d.ts +68 -0
- package/dist/core/capabilities/projects-local.js +172 -0
- package/dist/core/capabilities/publish-build.d.ts +13 -2
- package/dist/core/capabilities/publish-build.js +87 -21
- package/dist/core/capabilities/publish-download.js +1 -1
- package/dist/core/capabilities/runtime-control.d.ts +7 -2
- package/dist/core/capabilities/runtime-control.js +27 -1
- package/dist/core/capabilities/seeing/args.d.ts +2 -2
- package/dist/core/capabilities/store-checks.d.ts +45 -0
- package/dist/core/capabilities/store-checks.js +85 -0
- package/dist/core/capabilities/store-details.d.ts +37 -0
- package/dist/core/capabilities/store-details.js +94 -0
- package/dist/core/capabilities/summer-bundle.d.ts +21 -2
- package/dist/core/capabilities/summer-bundle.js +54 -7
- package/dist/core/capabilities/sync-items.d.ts +89 -0
- package/dist/core/capabilities/sync-items.js +352 -0
- package/dist/core/capabilities/tool-dispatch.js +66 -6
- package/dist/core/capabilities/video-ogv.d.ts +49 -0
- package/dist/core/capabilities/video-ogv.js +260 -0
- package/dist/core/capability-skew.js +2 -2
- package/dist/core/cli-usage-beacon.js +26 -2
- package/dist/core/cli-usage.d.ts +5 -2
- package/dist/core/cli-usage.js +8 -2
- package/dist/core/engine-install.d.ts +53 -0
- package/dist/core/engine-install.js +93 -0
- package/dist/core/engine-installer.d.ts +134 -0
- package/dist/core/engine-installer.js +760 -0
- package/dist/core/engine-launch.d.ts +126 -0
- package/dist/core/engine-launch.js +398 -0
- package/dist/core/engine-self-start.d.ts +63 -0
- package/dist/core/engine-self-start.js +282 -0
- package/dist/core/engine.d.ts +7 -1
- package/dist/core/engine.js +9 -2
- package/dist/core/error-codes.d.ts +4 -1
- package/dist/core/error-codes.js +7 -3
- package/dist/core/library-read.d.ts +2 -2
- package/dist/core/library-search.d.ts +2 -2
- package/dist/core/local-project.d.ts +50 -0
- package/dist/core/local-project.js +234 -0
- package/dist/core/ops/areas.js +9 -3
- package/dist/core/ops/catalog.d.ts +9 -0
- package/dist/core/ops/catalog.js +56 -4
- package/dist/core/ops/engine-answer.d.ts +4 -1
- package/dist/core/ops/engine-answer.js +16 -3
- package/dist/core/ops/engine.js +4 -4
- package/dist/core/ops/save-files.d.ts +6 -1
- package/dist/core/ops/save-files.js +67 -50
- package/dist/core/ops/search.js +9 -2
- package/dist/core/preview/input.d.ts +35 -0
- package/dist/core/preview/input.js +105 -0
- package/dist/core/preview/launch-config.d.ts +52 -0
- package/dist/core/preview/launch-config.js +137 -0
- package/dist/core/preview/page.d.ts +22 -0
- package/dist/core/preview/page.js +232 -0
- package/dist/core/preview/preview-source.d.ts +179 -0
- package/dist/core/preview/preview-source.js +350 -0
- package/dist/core/preview/server.d.ts +26 -0
- package/dist/core/preview/server.js +218 -0
- package/dist/core/telemetry.d.ts +20 -16
- package/dist/core/telemetry.js +92 -16
- package/dist/core/templates.d.ts +20 -0
- package/dist/core/templates.js +300 -3
- package/dist/core/tool-profiles.d.ts +2 -2
- package/dist/core/tool-profiles.js +1 -0
- package/dist/core/usage-fields.d.ts +41 -0
- package/dist/core/usage-fields.js +150 -0
- package/dist/core/util/sleep.d.ts +3 -0
- package/dist/core/util/sleep.js +12 -0
- package/dist/installer/agent-config.d.ts +29 -0
- package/dist/installer/agent-config.js +75 -11
- package/dist/installer/agent-detect.d.ts +44 -0
- package/dist/installer/agent-detect.js +166 -0
- package/dist/installer/agent-table.d.ts +19 -0
- package/dist/installer/agent-table.js +130 -0
- package/dist/installer/setup-error.d.ts +19 -0
- package/dist/installer/setup-error.js +34 -0
- package/dist/installer/version-check.d.ts +5 -0
- package/dist/installer/version-check.js +21 -0
- package/dist/mcp/ops-surface.d.ts +7 -0
- package/dist/mcp/ops-surface.js +40 -3
- package/dist/mcp/server.d.ts +14 -4
- package/dist/mcp/server.js +42 -8
- package/dist/mcp/tool-surface.d.ts +8 -0
- package/dist/mcp/tool-surface.js +19 -2
- package/dist/mcp/tools/creator-tools.js +92 -14
- package/dist/mcp/tools/debug-tools.js +2 -0
- package/dist/mcp/tools/local-project-tools.d.ts +6 -0
- package/dist/mcp/tools/local-project-tools.js +33 -0
- package/dist/mcp/tools/navigation-tools.js +2 -1
- package/dist/mcp/tools/project-tools.js +5 -8
- package/dist/mcp/tools/seeing-tools.js +1 -1
- package/dist/mcp/tools/visual-tools.js +1 -1
- package/dist/mcp/tools/with-engine.js +1 -1
- package/dist/project-memory/agent-instructions.js +11 -3
- package/gemini-extension.json +3 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
- package/library/skills/README.md +12 -8
- package/library/skills/animated-loop/SKILL.md +10 -16
- package/library/skills/cinematic-cutscene/SKILL.md +7 -18
- package/library/skills/headless-scripting/SKILL.md +7 -4
- package/library/skills/in-game-purchases/SKILL.md +25 -8
- package/library/skills/in-game-purchases/resource.yaml +2 -1
- package/library/skills/launch-ready/SKILL.md +351 -0
- package/library/skills/launch-ready/resource.yaml +45 -0
- package/library/skills/make-content/SKILL.md +202 -0
- package/library/skills/make-content/resource.yaml +32 -0
- package/library/skills/make-game/SKILL.md +6 -4
- package/library/skills/make-game/resource.yaml +2 -1
- package/library/skills/navigate-summer/SKILL.md +4 -2
- package/library/skills/new-project/SKILL.md +15 -22
- package/library/skills/new-project/resource.yaml +2 -1
- package/library/skills/painted-25d-world/SKILL.md +366 -0
- package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
- package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
- package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
- package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
- package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
- package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
- package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
- package/library/skills/painted-25d-world/resource.yaml +39 -0
- package/library/skills/publish-your-game/SKILL.md +13 -14
- package/library/skills/publish-your-game/resource.yaml +4 -3
- package/library/skills/release-checklist/SKILL.md +188 -0
- package/library/skills/release-checklist/resource.yaml +40 -0
- package/library/skills/release-readiness/SKILL.md +1 -1
- package/library/skills/release-readiness/resource.yaml +1 -1
- package/library/skills/store-art/SKILL.md +1 -1
- package/library/skills/store-art/resource.yaml +1 -1
- package/library/skills/store-listing/SKILL.md +1 -1
- package/library/skills/store-listing/resource.yaml +1 -1
- package/library/skills/summer-cli/SKILL.md +2 -0
- package/library/skills/summer-store/SKILL.md +295 -39
- package/library/skills/summer-store/resource.yaml +9 -3
- package/library/skills/trailer-shot/SKILL.md +3 -3
- package/library/templates/README.md +5 -0
- package/library/tools/content-make/resource.yaml +132 -0
- package/library/tools/frame-shot/GUIDE.md +1 -1
- package/library/tools/go-live/resource.yaml +67 -0
- package/library/tools/import-from-url/GUIDE.md +1 -0
- package/library/tools/import-from-url-batch/GUIDE.md +1 -1
- package/library/tools/play/GUIDE.md +2 -0
- package/library/tools/projects-create-local/resource.yaml +55 -0
- package/library/tools/projects-open-local/resource.yaml +51 -0
- package/library/tools/publish-build/resource.yaml +3 -3
- package/library/tools/screenshot/GUIDE.md +1 -1
- package/library/tools/store-set-details/resource.yaml +114 -0
- package/library/tools/sync-items/resource.yaml +72 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +6 -0
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +3 -1
- package/registry/generated/index.json +463 -37
- package/registry/generated/marketplace.claude.json +1 -1
- package/registry/generated/plugin.claude-directory.json +4 -2
- package/registry/generated/plugin.claude.json +3 -1
- package/registry/generated/plugin.codex.json +5 -3
- package/registry/generated/plugin.cursor.json +4 -2
- package/registry/generated/plugin.factory.json +4 -2
- package/registry/generated/server.json +2 -2
- package/registry/generated/skills-index.md +12 -8
- package/registry/generated/skills-registry.json +62 -5
- package/registry/operations/legacy-names.json +40 -0
- package/registry/operations/public-catalog.json +10144 -387
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
extends Node3D
|
|
2
|
+
## Builds a location's blockout from a JSON spec, so the same file drives the engine and the engine-free
|
|
3
|
+
## wireframe preview over the concept (preview.py). Put this node as the location's `Painted` child.
|
|
4
|
+
## Spec: {"objects": [{"name", "kind", "color", "solid", ...}]}, metres, y up:
|
|
5
|
+
## box pos (centre), size [x, y, z], yaw / rot_x / rot_z (degrees), scale
|
|
6
|
+
## prism pos, size (a PrismMesh: the ridge along z), yaw / rot_x / rot_z
|
|
7
|
+
## cyl pos (base centre), r_bot, r_top, height
|
|
8
|
+
## sphere pos (centre), radius, sy (vertical squash)
|
|
9
|
+
## poly points [[x, z], ...], top, depth: flat ground or a quay, extruded down, two-sided collision
|
|
10
|
+
## wall segments [[[x, z], [x, z]], ...], height: invisible blockers along water and ledges (leave gaps at
|
|
11
|
+
## pier roots and doorways)
|
|
12
|
+
## Each solid object gets a StaticBody3D with a matching shape; every mesh gets one flat colour from COLORS that
|
|
13
|
+
## tells the painter what it is (the paint-over prompt repeats them as a colour key). Name door and step slabs
|
|
14
|
+
## "..._door" / "..._steps": painted_location.gd hides them in the game. A gable roof is two parts: a
|
|
15
|
+
## wall-coloured prism for the gable plus two thin slanted roof slabs with overhang; a single roof-coloured prism
|
|
16
|
+
## makes the painter paint roof on the gable wall.
|
|
17
|
+
|
|
18
|
+
@export_file("*.json") var spec := "res://painted/blockout.json"
|
|
19
|
+
|
|
20
|
+
## Colour key: red tile roof, yellow thatch, dark blue slate, cream plaster, grey stone, brown wood and doors,
|
|
21
|
+
## green grass and leaves, pink flower beds, orange lamps and fruit, light brown cobbles and dirt, blue water.
|
|
22
|
+
const COLORS := {
|
|
23
|
+
"tile": Color(0.78, 0.25, 0.18), "thatch": Color(0.88, 0.72, 0.32), "slate": Color(0.22, 0.26, 0.42),
|
|
24
|
+
"plaster": Color(0.93, 0.88, 0.76), "stone": Color(0.55, 0.55, 0.55), "rock": Color(0.42, 0.4, 0.44),
|
|
25
|
+
"wood": Color(0.5, 0.33, 0.18), "door": Color(0.42, 0.26, 0.14), "crate": Color(0.66, 0.48, 0.28),
|
|
26
|
+
"grass": Color(0.36, 0.62, 0.25), "leaf": Color(0.2, 0.55, 0.2), "bush": Color(0.3, 0.55, 0.22),
|
|
27
|
+
"bark": Color(0.35, 0.22, 0.12), "flower": Color(0.95, 0.55, 0.75), "lamp": Color(1.0, 0.65, 0.2),
|
|
28
|
+
"cobble": Color(0.72, 0.6, 0.45), "dirt": Color(0.72, 0.56, 0.38), "water": Color(0.1, 0.35, 0.5),
|
|
29
|
+
"canvas": Color(0.9, 0.3, 0.3), "iron": Color(0.15, 0.15, 0.17), "glass": Color(0.35, 0.5, 0.75),
|
|
30
|
+
"floor": Color(0.6, 0.42, 0.25), "rug": Color(0.6, 0.15, 0.2), "fire": Color(1.0, 0.5, 0.1),
|
|
31
|
+
"white": Color(0.97, 0.97, 0.97),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func _ready() -> void:
|
|
35
|
+
var data: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(spec))
|
|
36
|
+
for o in data.objects:
|
|
37
|
+
if o.kind == "wall":
|
|
38
|
+
_walls(o)
|
|
39
|
+
else:
|
|
40
|
+
_build(o)
|
|
41
|
+
|
|
42
|
+
## Invisible collision only: keeps the player out of the water and on the piers.
|
|
43
|
+
func _walls(o: Dictionary) -> void:
|
|
44
|
+
var body := StaticBody3D.new()
|
|
45
|
+
body.name = o.name
|
|
46
|
+
for seg in o.segments:
|
|
47
|
+
var a := Vector3(seg[0][0], 0, seg[0][1])
|
|
48
|
+
var b := Vector3(seg[1][0], 0, seg[1][1])
|
|
49
|
+
var cs := CollisionShape3D.new()
|
|
50
|
+
var s := BoxShape3D.new()
|
|
51
|
+
s.size = Vector3(a.distance_to(b) + 0.1, o.height, 0.2)
|
|
52
|
+
cs.shape = s
|
|
53
|
+
cs.position = (a + b) / 2.0 + Vector3(0, o.height / 2.0, 0)
|
|
54
|
+
cs.rotation.y = atan2(-(b.z - a.z), b.x - a.x)
|
|
55
|
+
body.add_child(cs)
|
|
56
|
+
add_child(body)
|
|
57
|
+
|
|
58
|
+
func _build(o: Dictionary) -> void:
|
|
59
|
+
var mesh: Mesh
|
|
60
|
+
var shape: Shape3D
|
|
61
|
+
var offset := Vector3.ZERO
|
|
62
|
+
match o.kind:
|
|
63
|
+
"box":
|
|
64
|
+
var b := BoxMesh.new()
|
|
65
|
+
b.size = _v(o.size)
|
|
66
|
+
mesh = b
|
|
67
|
+
var s := BoxShape3D.new()
|
|
68
|
+
s.size = b.size
|
|
69
|
+
shape = s
|
|
70
|
+
"prism":
|
|
71
|
+
var p := PrismMesh.new()
|
|
72
|
+
p.size = _v(o.size)
|
|
73
|
+
mesh = p
|
|
74
|
+
shape = p.create_convex_shape()
|
|
75
|
+
"cyl":
|
|
76
|
+
var c := CylinderMesh.new()
|
|
77
|
+
c.top_radius = o.r_top
|
|
78
|
+
c.bottom_radius = o.r_bot
|
|
79
|
+
c.height = o.height
|
|
80
|
+
c.radial_segments = 24
|
|
81
|
+
mesh = c
|
|
82
|
+
offset.y = o.height / 2.0
|
|
83
|
+
var s := CylinderShape3D.new()
|
|
84
|
+
s.radius = maxf(o.r_top, o.r_bot)
|
|
85
|
+
s.height = o.height
|
|
86
|
+
shape = s
|
|
87
|
+
"sphere":
|
|
88
|
+
var sp := SphereMesh.new()
|
|
89
|
+
sp.radius = o.radius
|
|
90
|
+
sp.height = o.radius * 2.0
|
|
91
|
+
mesh = sp
|
|
92
|
+
var s := SphereShape3D.new()
|
|
93
|
+
s.radius = o.radius
|
|
94
|
+
shape = s
|
|
95
|
+
"poly":
|
|
96
|
+
mesh = _extrude(o.points, o.top, o.depth)
|
|
97
|
+
shape = mesh.create_trimesh_shape()
|
|
98
|
+
(shape as ConcavePolygonShape3D).backface_collision = true
|
|
99
|
+
var mi := MeshInstance3D.new()
|
|
100
|
+
mi.name = o.name
|
|
101
|
+
mi.mesh = mesh
|
|
102
|
+
var mat := StandardMaterial3D.new()
|
|
103
|
+
mat.albedo_color = COLORS.get(o.color, Color.MAGENTA)
|
|
104
|
+
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
|
|
105
|
+
mi.material_override = mat
|
|
106
|
+
var holder: Node3D = mi
|
|
107
|
+
if o.get("solid", false):
|
|
108
|
+
var body := StaticBody3D.new()
|
|
109
|
+
body.name = o.name + "Body"
|
|
110
|
+
var cs := CollisionShape3D.new()
|
|
111
|
+
cs.shape = shape
|
|
112
|
+
body.add_child(cs)
|
|
113
|
+
body.add_child(mi)
|
|
114
|
+
holder = body
|
|
115
|
+
if o.kind != "poly":
|
|
116
|
+
holder.position = _v(o.pos) + offset
|
|
117
|
+
holder.rotation_degrees = Vector3(o.get("rot_x", 0.0), o.get("yaw", 0.0), o.get("rot_z", 0.0))
|
|
118
|
+
if o.kind == "sphere" and o.has("sy"):
|
|
119
|
+
mi.scale = Vector3(1, o.sy, 1)
|
|
120
|
+
if o.has("scale"):
|
|
121
|
+
holder.scale = _v(o.scale)
|
|
122
|
+
add_child(holder)
|
|
123
|
+
|
|
124
|
+
## A flat polygon (x, z points) extruded from top down by depth, with side walls.
|
|
125
|
+
func _extrude(points: Array, top: float, depth: float) -> ArrayMesh:
|
|
126
|
+
var pts := PackedVector2Array()
|
|
127
|
+
for p in points:
|
|
128
|
+
pts.append(Vector2(p[0], p[1]))
|
|
129
|
+
var st := SurfaceTool.new()
|
|
130
|
+
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
|
131
|
+
var tri := Geometry2D.triangulate_polygon(pts)
|
|
132
|
+
for i in range(0, tri.size(), 3):
|
|
133
|
+
for k in [0, 2, 1]:
|
|
134
|
+
var p := pts[tri[i + k]]
|
|
135
|
+
st.add_vertex(Vector3(p.x, top, p.y))
|
|
136
|
+
for i in pts.size():
|
|
137
|
+
var a := pts[i]
|
|
138
|
+
var b := pts[(i + 1) % pts.size()]
|
|
139
|
+
var a0 := Vector3(a.x, top, a.y)
|
|
140
|
+
var b0 := Vector3(b.x, top, b.y)
|
|
141
|
+
var a1 := a0 - Vector3(0, depth, 0)
|
|
142
|
+
var b1 := b0 - Vector3(0, depth, 0)
|
|
143
|
+
for v in [a0, b0, b1, a0, b1, a1]:
|
|
144
|
+
st.add_vertex(v)
|
|
145
|
+
st.generate_normals()
|
|
146
|
+
return st.commit()
|
|
147
|
+
|
|
148
|
+
func _v(a: Array) -> Vector3:
|
|
149
|
+
return Vector3(a[0], a[1], a[2])
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
## Renders a location's plain, lit blockout through its PaintCamera into a PNG of an exact size, then quits.
|
|
3
|
+
## Make a scene with only this node, set `level` (the world scene) and `camera_path`, and play it. The PNG is the
|
|
4
|
+
## structure image for the paint-over. Keep size at the painting's aspect (16:9). Needs a rendering run (the
|
|
5
|
+
## editor's play, or a windowed run); a --headless run renders nothing.
|
|
6
|
+
|
|
7
|
+
@export var level: PackedScene
|
|
8
|
+
@export var size := Vector2i(2048, 1152)
|
|
9
|
+
@export var out_path := "res://painted/blockout.png"
|
|
10
|
+
## The location's paint camera inside `level`.
|
|
11
|
+
@export var camera_path := "Village/PaintCamera"
|
|
12
|
+
|
|
13
|
+
func _ready() -> void:
|
|
14
|
+
var vp := SubViewport.new()
|
|
15
|
+
vp.size = size
|
|
16
|
+
vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
|
17
|
+
vp.own_world_3d = true
|
|
18
|
+
add_child(vp)
|
|
19
|
+
var lvl := level.instantiate()
|
|
20
|
+
# no painting: painted_world.gd then shows the plain blockout, lit, through the paint camera
|
|
21
|
+
for node in lvl.find_children("*", "Node3D", true, false):
|
|
22
|
+
if "paint" in node:
|
|
23
|
+
node.paint = null
|
|
24
|
+
vp.add_child(lvl)
|
|
25
|
+
await get_tree().process_frame
|
|
26
|
+
(lvl.get_node(camera_path) as Camera3D).make_current()
|
|
27
|
+
for i in 4:
|
|
28
|
+
await RenderingServer.frame_post_draw
|
|
29
|
+
var img := vp.get_texture().get_image()
|
|
30
|
+
img.save_png(out_path)
|
|
31
|
+
print("PAINT_VIEW_SAVED ", ProjectSettings.globalize_path(out_path), " ", img.get_size())
|
|
32
|
+
get_tree().quit()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Projects one painted picture onto every mesh that uses this material, from the fixed paint camera.
|
|
2
|
+
// UV = where the surface point sits in the paint camera's orthographic frame, so no mesh needs UVs.
|
|
3
|
+
// Unshaded: the painting already holds all the light. Keep the scene tonemap Linear, exposure 1.
|
|
4
|
+
shader_type spatial;
|
|
5
|
+
render_mode unshaded, cull_disabled, shadows_disabled;
|
|
6
|
+
|
|
7
|
+
uniform sampler2D paint : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
|
|
8
|
+
uniform vec3 paint_origin; // PaintCamera.global_position
|
|
9
|
+
uniform vec3 paint_right; // PaintCamera.global_basis.x
|
|
10
|
+
uniform vec3 paint_up; // PaintCamera.global_basis.y
|
|
11
|
+
uniform vec2 paint_size; // frame width, height in metres (Camera3D.size with KEEP_WIDTH, height = width * H / W)
|
|
12
|
+
|
|
13
|
+
varying vec3 world_pos;
|
|
14
|
+
|
|
15
|
+
void vertex() {
|
|
16
|
+
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void fragment() {
|
|
20
|
+
vec3 d = world_pos - paint_origin;
|
|
21
|
+
vec2 uv = vec2(dot(d, paint_right) / paint_size.x + 0.5, 0.5 - dot(d, paint_up) / paint_size.y);
|
|
22
|
+
ALBEDO = texture(paint, uv).rgb;
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
extends Node
|
|
2
|
+
## Verify the projection: renders the painted world through a location's PaintCamera and saves the PNG. It must
|
|
3
|
+
## match the painting (compare them, or overlay). `summer_screenshot` of the scene cannot do this when the blockout
|
|
4
|
+
## is built by a script: scene renders do not run scripts. Make a scene with only this node and play it.
|
|
5
|
+
|
|
6
|
+
@export var level: PackedScene
|
|
7
|
+
@export var camera_path := "Village/PaintCamera"
|
|
8
|
+
@export var size := Vector2i(2048, 1152)
|
|
9
|
+
@export var out_path := "res://painted/paintcam_check.png"
|
|
10
|
+
|
|
11
|
+
func _ready() -> void:
|
|
12
|
+
var vp := SubViewport.new()
|
|
13
|
+
vp.size = size
|
|
14
|
+
vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
|
15
|
+
vp.own_world_3d = true
|
|
16
|
+
add_child(vp)
|
|
17
|
+
var lvl := level.instantiate()
|
|
18
|
+
vp.add_child(lvl)
|
|
19
|
+
await get_tree().process_frame
|
|
20
|
+
(lvl.get_node(camera_path) as Camera3D).make_current()
|
|
21
|
+
for i in 6:
|
|
22
|
+
await RenderingServer.frame_post_draw
|
|
23
|
+
vp.get_texture().get_image().save_png(out_path)
|
|
24
|
+
print("PAINTCAM_SAVED ", ProjectSettings.globalize_path(out_path))
|
|
25
|
+
get_tree().quit()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
extends Area3D
|
|
2
|
+
## A door between painted locations. When the player walks in, the world moves them into `to` at `spawn`.
|
|
3
|
+
## Put `spawn` (a Marker3D) inside the target room or outside the target door, clear of that room's own door
|
|
4
|
+
## trigger, or the player bounces straight back.
|
|
5
|
+
|
|
6
|
+
@export var to: NodePath
|
|
7
|
+
@export var spawn: NodePath
|
|
8
|
+
|
|
9
|
+
func _ready() -> void:
|
|
10
|
+
body_entered.connect(_on_body_entered)
|
|
11
|
+
|
|
12
|
+
func _on_body_entered(body: Node) -> void:
|
|
13
|
+
var world := get_parent()
|
|
14
|
+
while world != null and not world.has_method("go"):
|
|
15
|
+
world = world.get_parent()
|
|
16
|
+
if world != null and body == world.player:
|
|
17
|
+
world.go(get_node(to), (get_node(spawn) as Node3D).global_position)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
extends Node3D
|
|
2
|
+
## One painted frame: a PaintCamera, the blockout under Painted, and the paint-over projected onto it from
|
|
3
|
+
## that camera. With no `paint` set it shows the plain blockout (play it and capture that frame for the paint-over).
|
|
4
|
+
## Children: PaintCamera (Camera3D, orthographic, KEEP_WIDTH), Painted (Node3D with every block).
|
|
5
|
+
|
|
6
|
+
const PROJECTION := preload("res://painted/paint_projection.gdshader")
|
|
7
|
+
|
|
8
|
+
## The paint-over for this frame (4K, imported lossless).
|
|
9
|
+
@export var paint: Texture2D
|
|
10
|
+
## Rooms: frame the whole painting like a stage. Outdoors: follow the player.
|
|
11
|
+
@export var frame_whole := false
|
|
12
|
+
|
|
13
|
+
@onready var paint_cam: Camera3D = $PaintCamera
|
|
14
|
+
var paint_size := Vector2.ZERO
|
|
15
|
+
## Average colour along the painting's border: the clear colour while the player is here.
|
|
16
|
+
var edge_color := Color(0.1, 0.1, 0.1)
|
|
17
|
+
|
|
18
|
+
func _ready() -> void:
|
|
19
|
+
if paint == null:
|
|
20
|
+
return
|
|
21
|
+
paint_size = Vector2(paint_cam.size, paint_cam.size * paint.get_height() / float(paint.get_width()))
|
|
22
|
+
var mat := ShaderMaterial.new()
|
|
23
|
+
mat.shader = PROJECTION
|
|
24
|
+
mat.set_shader_parameter("paint", paint)
|
|
25
|
+
mat.set_shader_parameter("paint_origin", paint_cam.global_position)
|
|
26
|
+
mat.set_shader_parameter("paint_right", paint_cam.global_basis.x)
|
|
27
|
+
mat.set_shader_parameter("paint_up", paint_cam.global_basis.y)
|
|
28
|
+
mat.set_shader_parameter("paint_size", paint_size)
|
|
29
|
+
for mi in $Painted.find_children("*", "MeshInstance3D", true, false):
|
|
30
|
+
# door slabs and steps stick out of the walls in the blockout; the painting already shows them
|
|
31
|
+
if mi.name.containsn("door") or mi.name.containsn("steps"):
|
|
32
|
+
mi.visible = false
|
|
33
|
+
continue
|
|
34
|
+
(mi as MeshInstance3D).material_override = mat
|
|
35
|
+
(mi as MeshInstance3D).cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
|
|
36
|
+
edge_color = _edge_color()
|
|
37
|
+
|
|
38
|
+
## Where p sits in this frame's picture plane, in metres from the frame centre.
|
|
39
|
+
func plane(p: Vector3) -> Vector2:
|
|
40
|
+
var d := p - paint_cam.global_position
|
|
41
|
+
return Vector2(d.dot(paint_cam.global_basis.x), d.dot(paint_cam.global_basis.y))
|
|
42
|
+
|
|
43
|
+
func _edge_color() -> Color:
|
|
44
|
+
var img := paint.get_image()
|
|
45
|
+
if img == null:
|
|
46
|
+
return edge_color
|
|
47
|
+
if img.is_compressed():
|
|
48
|
+
img.decompress()
|
|
49
|
+
var w := img.get_width() - 1
|
|
50
|
+
var h := img.get_height() - 1
|
|
51
|
+
var sum := Color(0, 0, 0, 0)
|
|
52
|
+
for i in 32:
|
|
53
|
+
var t := i / 31.0
|
|
54
|
+
for p in [Vector2i(int(t * w), 0), Vector2i(int(t * w), h), Vector2i(0, int(t * h)), Vector2i(w, int(t * h))]:
|
|
55
|
+
sum += img.get_pixelv(p)
|
|
56
|
+
return Color(sum.r / 128.0, sum.g / 128.0, sum.b / 128.0)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
extends CharacterBody3D
|
|
2
|
+
## Sprite character on a capsule. Input moves in screen directions: up = away from the camera on the ground.
|
|
3
|
+
## The sprite is a vertical Y-billboard, stretched by 1 / cos(camera pitch) so it reads at full height from the
|
|
4
|
+
## tilted camera. Eight facings picked by 45 degree sectors; a missing diagonal falls back to its side row.
|
|
5
|
+
## The gait depends on the game type. Cozy, exploration and RPG games walk by default (about 1.8 m/s, walk cycle)
|
|
6
|
+
## and run on Shift (about 4 m/s, run cycle); action games may run by default (move_speed about 4.2). The cycle is
|
|
7
|
+
## chosen by speed (run cycle from `run_cycle_from`), and speed_scale = speed / the cycle's natural speed, so the
|
|
8
|
+
## feet never slide. Never play the run cycle for the default gait of a cozy game.
|
|
9
|
+
## Sheet: one row per animation (idle_down, walk_up_left, run_right, ...), fixed cell size. The json beside it:
|
|
10
|
+
## {"cell_w", "cell_h", "frames", "rows", "fps": {"idle": 6, "walk": 10, "run": 14}, "height_m": 1.8,
|
|
11
|
+
## "stride_m": {"walk": 0.94, "run": 1.68}, "cycles_per_s": {"walk": 1.25, "run": 1.75}}
|
|
12
|
+
## (stride_m = metres per cycle at height_m).
|
|
13
|
+
|
|
14
|
+
const DIRS := ["right", "up_right", "up", "up_left", "left", "down_left", "down", "down_right"]
|
|
15
|
+
|
|
16
|
+
@export var sheet: Texture2D
|
|
17
|
+
@export_file("*.json") var sheet_json := ""
|
|
18
|
+
## About door height / 1.15: 2.15 m next to 2.5 m doors.
|
|
19
|
+
@export var height_m := 2.15
|
|
20
|
+
## m/s, the default gait. Cozy / exploration / RPG: 1.8 (walk cycle). Action: about 4.2 (run cycle).
|
|
21
|
+
@export var move_speed := 1.8
|
|
22
|
+
## m/s while Shift is held (run cycle). Cozy: 4.0. Action: up to 1.8x the run cycle's natural speed.
|
|
23
|
+
@export var sprint_speed := 4.0
|
|
24
|
+
## m/s from which the run cycle plays instead of the walk cycle.
|
|
25
|
+
@export var run_cycle_from := 3.0
|
|
26
|
+
## Tests and agents drive the character with this instead of keys (x = screen right, y = screen up):
|
|
27
|
+
## length up to 1 is the default gait, above 1 sprints.
|
|
28
|
+
var input_override := Vector2.ZERO
|
|
29
|
+
var cam_basis := Basis()
|
|
30
|
+
var sprite: AnimatedSprite3D
|
|
31
|
+
var facing := "down"
|
|
32
|
+
var meta: Dictionary
|
|
33
|
+
|
|
34
|
+
func _ready() -> void:
|
|
35
|
+
meta = JSON.parse_string(FileAccess.get_file_as_string(sheet_json))
|
|
36
|
+
var shape := CollisionShape3D.new()
|
|
37
|
+
var cap := CapsuleShape3D.new()
|
|
38
|
+
cap.radius = 0.4
|
|
39
|
+
cap.height = height_m - 0.2
|
|
40
|
+
shape.shape = cap
|
|
41
|
+
shape.position.y = cap.height / 2.0
|
|
42
|
+
add_child(shape)
|
|
43
|
+
sprite = AnimatedSprite3D.new()
|
|
44
|
+
sprite.sprite_frames = _frames()
|
|
45
|
+
sprite.billboard = BaseMaterial3D.BILLBOARD_FIXED_Y
|
|
46
|
+
sprite.shaded = false
|
|
47
|
+
sprite.alpha_cut = SpriteBase3D.ALPHA_CUT_DISCARD # writes depth, so painted walls hide the character
|
|
48
|
+
add_child(sprite)
|
|
49
|
+
|
|
50
|
+
## Called by the world with the paint camera's basis.
|
|
51
|
+
func setup(view_basis: Basis) -> void:
|
|
52
|
+
cam_basis = view_basis
|
|
53
|
+
var pitch := asin(clampf(-view_basis.z.y, -1.0, 1.0))
|
|
54
|
+
var h := height_m / cos(pitch)
|
|
55
|
+
sprite.pixel_size = h / float(meta.cell_h)
|
|
56
|
+
sprite.position.y = h / 2.0
|
|
57
|
+
_play("idle", 0.0)
|
|
58
|
+
|
|
59
|
+
func _frames() -> SpriteFrames:
|
|
60
|
+
var sf := SpriteFrames.new()
|
|
61
|
+
sf.remove_animation("default")
|
|
62
|
+
for r in meta.rows.size():
|
|
63
|
+
var anim: String = meta.rows[r]
|
|
64
|
+
sf.add_animation(anim)
|
|
65
|
+
sf.set_animation_speed(anim, _fps(anim.get_slice("_", 0)))
|
|
66
|
+
for c in int(meta.frames):
|
|
67
|
+
var at := AtlasTexture.new()
|
|
68
|
+
at.atlas = sheet
|
|
69
|
+
at.region = Rect2(c * meta.cell_w, r * meta.cell_h, meta.cell_w, meta.cell_h)
|
|
70
|
+
sf.add_frame(anim, at)
|
|
71
|
+
return sf
|
|
72
|
+
|
|
73
|
+
## fps in the json: one number, or a dictionary by kind ("walk").
|
|
74
|
+
func _fps(kind: String) -> float:
|
|
75
|
+
var fps = meta.get("fps", 8.0)
|
|
76
|
+
if fps is Dictionary:
|
|
77
|
+
return float(fps.get(kind, 8.0))
|
|
78
|
+
return float(fps)
|
|
79
|
+
|
|
80
|
+
## Ground speed (m/s) at which this kind's cycle keeps the feet still: stride (scaled to this height) x cycles/s.
|
|
81
|
+
## Without measurements: the generated cycles' typical values (walk 0.57, run 1.63 body heights per second).
|
|
82
|
+
func natural_speed(kind: String) -> float:
|
|
83
|
+
var stride = meta.get("stride_m", {}).get(kind)
|
|
84
|
+
if stride == null:
|
|
85
|
+
return height_m * (1.63 if kind == "run" else 0.57)
|
|
86
|
+
var cycles = meta.get("cycles_per_s", {}).get(kind, _fps(kind) / float(meta.frames))
|
|
87
|
+
return float(stride) * height_m / float(meta.get("height_m", height_m)) * float(cycles)
|
|
88
|
+
|
|
89
|
+
## The cycle for a ground speed: the walk cycle below `run_cycle_from`, the run cycle from there.
|
|
90
|
+
func cycle_for(speed: float) -> String:
|
|
91
|
+
if not sprite.sprite_frames.has_animation("run_down") or speed < run_cycle_from:
|
|
92
|
+
return "walk"
|
|
93
|
+
return "run"
|
|
94
|
+
|
|
95
|
+
func _play(kind: String, speed: float) -> void:
|
|
96
|
+
var side := "left" if facing.ends_with("left") else ("right" if facing.ends_with("right") else facing)
|
|
97
|
+
for anim in [kind + "_" + facing, kind + "_" + side, kind + "_down"]:
|
|
98
|
+
if sprite.sprite_frames.has_animation(anim):
|
|
99
|
+
sprite.play(anim)
|
|
100
|
+
break
|
|
101
|
+
sprite.speed_scale = 1.0 if kind == "idle" else clampf(speed / natural_speed(kind), 0.25, 4.0)
|
|
102
|
+
|
|
103
|
+
func _physics_process(delta: float) -> void:
|
|
104
|
+
var v := input_override
|
|
105
|
+
var speed := move_speed
|
|
106
|
+
if v != Vector2.ZERO:
|
|
107
|
+
speed = sprint_speed if v.length() > 1.05 else move_speed
|
|
108
|
+
else:
|
|
109
|
+
v = Input.get_vector("ui_left", "ui_right", "ui_down", "ui_up")
|
|
110
|
+
if Input.is_key_pressed(KEY_SHIFT):
|
|
111
|
+
speed = sprint_speed
|
|
112
|
+
if v.length() < 0.1:
|
|
113
|
+
speed = 0.0
|
|
114
|
+
var right := Vector3(cam_basis.x.x, 0, cam_basis.x.z).normalized()
|
|
115
|
+
var far := Vector3(-cam_basis.z.x, 0, -cam_basis.z.z).normalized()
|
|
116
|
+
var dir := (right * v.x + far * v.y).normalized()
|
|
117
|
+
velocity.x = dir.x * speed
|
|
118
|
+
velocity.z = dir.z * speed
|
|
119
|
+
velocity.y = 0.0 if is_on_floor() else velocity.y - 20.0 * delta
|
|
120
|
+
move_and_slide()
|
|
121
|
+
if speed > 0.0:
|
|
122
|
+
facing = DIRS[wrapi(roundi(v.angle() / (PI / 4.0)), 0, 8)]
|
|
123
|
+
_play(cycle_for(speed), speed)
|
|
124
|
+
else:
|
|
125
|
+
_play("idle", 0.0)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
extends Node3D
|
|
2
|
+
## Painted 2.5D world: painted locations (painted_location.gd: Village, Cottage, ...), one player, one game camera.
|
|
3
|
+
## The game camera keeps the paint direction and only moves, so surfaces hidden in a painting stay hidden.
|
|
4
|
+
## Outdoors it shows about `view_width` metres and is clamped inside the painting with the REAL viewport aspect;
|
|
5
|
+
## rooms are framed whole. The clear colour is the current painting's edge colour.
|
|
6
|
+
## Proof shots from the real game window: run with `-- --shots=<dir>`; the player walks and runs through
|
|
7
|
+
## `shot_plan`, each step saves a JPG, then the game quits. Add your door walks to shot_plan.
|
|
8
|
+
|
|
9
|
+
@export var player_path: NodePath
|
|
10
|
+
@export var start_location: NodePath
|
|
11
|
+
## Metres across the screen outdoors: about 16 m, 7-8 hero heights.
|
|
12
|
+
@export var view_width := 16.0
|
|
13
|
+
## [name, screen input (x right, y up; length > 1 runs), seconds, "follow" or "full"]
|
|
14
|
+
@export var shot_plan: Array = [
|
|
15
|
+
["start", Vector2.ZERO, 0.6, "follow"],
|
|
16
|
+
["walk", Vector2(-1, 1).normalized(), 1.6, "follow"],
|
|
17
|
+
["run", Vector2(-1.8, -0.6), 0.9, "follow"],
|
|
18
|
+
["full_frame", Vector2.ZERO, 0.4, "full"],
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
@onready var game_cam: Camera3D = $GameCamera
|
|
22
|
+
@onready var player: CharacterBody3D = get_node(player_path)
|
|
23
|
+
var location: Node3D
|
|
24
|
+
var full_view := false
|
|
25
|
+
|
|
26
|
+
func _ready() -> void:
|
|
27
|
+
location = get_node(start_location)
|
|
28
|
+
if location.paint == null:
|
|
29
|
+
_capture_mode()
|
|
30
|
+
return
|
|
31
|
+
game_cam.projection = Camera3D.PROJECTION_ORTHOGONAL
|
|
32
|
+
game_cam.keep_aspect = Camera3D.KEEP_WIDTH
|
|
33
|
+
game_cam.far = location.paint_cam.far
|
|
34
|
+
game_cam.make_current()
|
|
35
|
+
_enter(location)
|
|
36
|
+
if player.has_method("setup"):
|
|
37
|
+
player.setup(location.paint_cam.global_basis)
|
|
38
|
+
for arg in OS.get_cmdline_user_args():
|
|
39
|
+
if arg.begins_with("--shots="):
|
|
40
|
+
_take_shots.call_deferred(arg.trim_prefix("--shots="))
|
|
41
|
+
|
|
42
|
+
## No painting yet: show the plain blockout through the paint camera, lit so its faces read for the painter
|
|
43
|
+
## (the painted game itself stays unshaded). Play it and capture the frame for the paint-over.
|
|
44
|
+
func _capture_mode() -> void:
|
|
45
|
+
location.paint_cam.make_current()
|
|
46
|
+
set_physics_process(false)
|
|
47
|
+
if player.has_method("setup"):
|
|
48
|
+
player.setup(location.paint_cam.global_basis)
|
|
49
|
+
var sun := DirectionalLight3D.new()
|
|
50
|
+
sun.rotation_degrees = Vector3(-50, 20, 0)
|
|
51
|
+
sun.light_energy = 1.2
|
|
52
|
+
add_child(sun)
|
|
53
|
+
var env := WorldEnvironment.new()
|
|
54
|
+
env.environment = Environment.new()
|
|
55
|
+
env.environment.background_mode = Environment.BG_COLOR
|
|
56
|
+
env.environment.background_color = Color(0.1, 0.1, 0.12)
|
|
57
|
+
env.environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
|
|
58
|
+
env.environment.ambient_light_color = Color.WHITE
|
|
59
|
+
env.environment.ambient_light_energy = 0.55
|
|
60
|
+
add_child(env)
|
|
61
|
+
|
|
62
|
+
## Doors call this: move the player into another painted location.
|
|
63
|
+
func go(to: Node3D, spawn: Vector3) -> void:
|
|
64
|
+
location = to
|
|
65
|
+
player.global_position = spawn
|
|
66
|
+
player.velocity = Vector3.ZERO
|
|
67
|
+
_enter(to)
|
|
68
|
+
|
|
69
|
+
func _enter(loc: Node3D) -> void:
|
|
70
|
+
game_cam.global_basis = loc.paint_cam.global_basis
|
|
71
|
+
RenderingServer.set_default_clear_color(loc.edge_color)
|
|
72
|
+
_physics_process(0.0)
|
|
73
|
+
|
|
74
|
+
## Viewport height / width. The editor's game view and phones are not 16:9.
|
|
75
|
+
func _aspect() -> float:
|
|
76
|
+
var vp := get_viewport().get_visible_rect().size
|
|
77
|
+
return vp.y / vp.x if vp.x > 0 else 9.0 / 16.0
|
|
78
|
+
|
|
79
|
+
func _physics_process(_delta: float) -> void:
|
|
80
|
+
var size: Vector2 = location.paint_size
|
|
81
|
+
var aspect := _aspect()
|
|
82
|
+
var fit := minf(size.x, size.y / aspect) # the widest view that stays inside the painting
|
|
83
|
+
var cam_basis: Basis = location.paint_cam.global_basis
|
|
84
|
+
if location.frame_whole or full_view:
|
|
85
|
+
game_cam.size = fit
|
|
86
|
+
game_cam.global_position = location.paint_cam.global_position
|
|
87
|
+
return
|
|
88
|
+
# keep the player inside the painting (move along the ground, not through the air)
|
|
89
|
+
var half := size / 2.0 - Vector2(1.0, 2.0)
|
|
90
|
+
var c: Vector2 = location.plane(player.global_position)
|
|
91
|
+
var clamped := c.clamp(-half, half)
|
|
92
|
+
if clamped != c:
|
|
93
|
+
var fwd := -cam_basis.z
|
|
94
|
+
var far := Vector3(fwd.x, 0, fwd.z).normalized()
|
|
95
|
+
player.global_position += cam_basis.x * (clamped.x - c.x) + far * (clamped.y - c.y) / far.dot(cam_basis.y)
|
|
96
|
+
game_cam.size = minf(view_width, fit)
|
|
97
|
+
var view := Vector2(game_cam.size, game_cam.size * aspect)
|
|
98
|
+
var t: Vector2 = location.plane(player.global_position + Vector3(0, 1, 0)).clamp(-(size - view) / 2.0, (size - view) / 2.0)
|
|
99
|
+
game_cam.global_position = location.paint_cam.global_position + cam_basis.x * t.x + cam_basis.y * t.y
|
|
100
|
+
|
|
101
|
+
func _take_shots(dir: String) -> void:
|
|
102
|
+
DirAccess.make_dir_recursive_absolute(dir)
|
|
103
|
+
for step in shot_plan:
|
|
104
|
+
full_view = step[3] == "full"
|
|
105
|
+
player.input_override = step[1]
|
|
106
|
+
await get_tree().create_timer(step[2]).timeout
|
|
107
|
+
await RenderingServer.frame_post_draw
|
|
108
|
+
get_viewport().get_texture().get_image().save_jpg(dir.path_join(str(step[0]) + ".jpg"), 0.92)
|
|
109
|
+
player.input_override = Vector2.ZERO
|
|
110
|
+
full_view = false
|
|
111
|
+
get_tree().quit()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Paint camera math without the engine: concept pixels <-> world positions.
|
|
2
|
+
Matches a Godot PaintCamera with rotation_degrees (-ELEV, AZIM, 0), orthographic, KEEP_WIDTH, centred on C.
|
|
3
|
+
Settings (environment): PAINT_W frame width in metres (40), PAINT_ELEV degrees down (30), PAINT_AZIM (45),
|
|
4
|
+
PAINT_C frame centre "x,y,z" (0,0,0), PAINT_IMG image size "2048,1152".
|
|
5
|
+
usage: python3 cam.py <height_m> <px,py> [<px,py> ...] world point on the plane y = height under each pixel
|
|
6
|
+
(bottom corners of walls at height 0 give a footprint; read a ridge at the eave height to place a roof)"""
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
W = float(os.environ.get("PAINT_W", "40"))
|
|
11
|
+
IMG = tuple(int(v) for v in os.environ.get("PAINT_IMG", "2048,1152").split(","))
|
|
12
|
+
H = W * IMG[1] / IMG[0]
|
|
13
|
+
C = tuple(float(v) for v in os.environ.get("PAINT_C", "0,0,0").split(","))
|
|
14
|
+
PITCH = math.radians(-float(os.environ.get("PAINT_ELEV", "30")))
|
|
15
|
+
YAW = math.radians(float(os.environ.get("PAINT_AZIM", "45")))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def ry(v, a):
|
|
19
|
+
x, y, z = v
|
|
20
|
+
return (x * math.cos(a) + z * math.sin(a), y, -x * math.sin(a) + z * math.cos(a))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def rx(v, a):
|
|
24
|
+
x, y, z = v
|
|
25
|
+
return (x, y * math.cos(a) - z * math.sin(a), y * math.sin(a) + z * math.cos(a))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
RIGHT = ry(rx((1, 0, 0), PITCH), YAW)
|
|
29
|
+
UP = ry(rx((0, 1, 0), PITCH), YAW)
|
|
30
|
+
FWD = ry(rx((0, 0, -1), PITCH), YAW)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def dot(a, b):
|
|
34
|
+
return sum(i * j for i, j in zip(a, b))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def to_px(w):
|
|
38
|
+
"""World point -> pixel in the painting."""
|
|
39
|
+
d = tuple(w[i] - C[i] for i in range(3))
|
|
40
|
+
return ((dot(d, RIGHT) / W + 0.5) * IMG[0], (0.5 - dot(d, UP) / H) * IMG[1])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def to_ground(px, py, h=0.0):
|
|
44
|
+
"""Pixel -> the world point on the plane y = h that the pixel shows."""
|
|
45
|
+
a = (px / IMG[0] - 0.5) * W
|
|
46
|
+
b = (0.5 - py / IMG[1]) * H
|
|
47
|
+
p0 = tuple(C[i] + a * RIGHT[i] + b * UP[i] for i in range(3))
|
|
48
|
+
t = (h - p0[1]) / FWD[1]
|
|
49
|
+
return tuple(round(p0[i] + t * FWD[i], 2) for i in range(3))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
import sys
|
|
54
|
+
h = float(sys.argv[1])
|
|
55
|
+
for s in sys.argv[2:]:
|
|
56
|
+
x, y = map(float, s.split(","))
|
|
57
|
+
print(s, "->", to_ground(x, y, h))
|