ue-mcp-plugin-voxel-plugin 0.1.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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/shared/voxelGraph.d.ts +31 -0
- package/dist/shared/voxelGraph.js +65 -0
- package/dist/shared/voxelGraph.js.map +1 -0
- package/dist/tasks/BakeHeightmap.d.ts +37 -0
- package/dist/tasks/BakeHeightmap.js +101 -0
- package/dist/tasks/BakeHeightmap.js.map +1 -0
- package/dist/tasks/ScatterMeshes.d.ts +25 -0
- package/dist/tasks/ScatterMeshes.js +52 -0
- package/dist/tasks/ScatterMeshes.js.map +1 -0
- package/dist/tasks/SpawnStamps.d.ts +23 -0
- package/dist/tasks/SpawnStamps.js +51 -0
- package/dist/tasks/SpawnStamps.js.map +1 -0
- package/knowledge/landscape.md +34 -0
- package/knowledge/pcg.md +50 -0
- package/package.json +43 -0
- package/ue-mcp.plugin.yml +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Lyon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# ue-mcp-plugin-voxel-plugin
|
|
2
|
+
|
|
3
|
+
[Voxel Plugin](https://voxelplugin.com) actions for [ue-mcp](https://github.com/db-lyon/ue-mcp).
|
|
4
|
+
|
|
5
|
+
Injects three actions into the existing built-in categories:
|
|
6
|
+
|
|
7
|
+
| Category | Action | What it does |
|
|
8
|
+
|-------------|---------------------------|-----------------------------------------------------------------------|
|
|
9
|
+
| `pcg` | `voxel_scatter_meshes` | Builds a PCG graph that scatters static meshes on a voxel terrain. |
|
|
10
|
+
| `pcg` | `voxel_spawn_stamps` | Builds a PCG graph that places voxel stamp assets. |
|
|
11
|
+
| `landscape` | `voxel_bake_heightmap` | Bakes a region of a voxel terrain into a standard Landscape heightmap.|
|
|
12
|
+
|
|
13
|
+
Plus two ready-to-run flows: `voxel_scatter_setup` and `voxel_stamps_setup`.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
In your project directory:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
ue-mcp plugin install ue-mcp-plugin-voxel-plugin
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The CLI runs `npm install --save`, validates the plugin manifest, adds an entry under `plugins:` in your `ue-mcp.yml`, and prints a restart instruction. Injected actions appear on the next ue-mcp server start.
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- ue-mcp `>= 1.0.15`.
|
|
28
|
+
- Voxel Plugin enabled in your `.uproject` (`Plugins[].Name == "Voxel"`). The installer warns at install time if it's missing.
|
|
29
|
+
- An active Voxel Plugin Pro subscription if your project's use case requires it - the upstream source repo is public-readable but commercial use is gated by the upstream license.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
The actions appear natively inside their host categories - no new tool to learn:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
pcg(action="voxel_scatter_meshes",
|
|
37
|
+
graphPath="/Game/PCG/Scatter",
|
|
38
|
+
mesh="/Game/Foliage/Rock.Rock",
|
|
39
|
+
density=0.25)
|
|
40
|
+
|
|
41
|
+
pcg(action="execute", graphPath="/Game/PCG/Scatter")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or via the bundled flow:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
flow(action="run", flowName="voxel_scatter_setup", params={
|
|
48
|
+
graphPath: "/Game/PCG/Scatter",
|
|
49
|
+
mesh: "/Game/Foliage/Rock.Rock"
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See `knowledge/pcg.md` and `knowledge/landscape.md` for the full parameter reference. The same content is attached to the host category's AI-facing docs at server start so MCP agents see it natively.
|
|
54
|
+
|
|
55
|
+
## Develop
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/db-lyon/ue-mcp-plugin-voxel-plugin.git
|
|
59
|
+
cd ue-mcp-plugin-voxel-plugin
|
|
60
|
+
npm install
|
|
61
|
+
npm run build
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The plugin compiles to `dist/`. To test against a local checkout of ue-mcp, `npm link` this package and `npm link ue-mcp-plugin-voxel-plugin` from your project directory.
|
|
65
|
+
|
|
66
|
+
## Why a plugin?
|
|
67
|
+
|
|
68
|
+
ue-mcp's plugin model injects new actions into existing built-in categories rather than creating a separate tool. The agent already working in `pcg` discovers `voxel_scatter_meshes` exactly when it's relevant - it does not need to know there is a separate Voxel surface to open. See the [ue-mcp plugin docs](https://db-lyon.github.io/ue-mcp/plugins/) for the full author contract.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT - see `LICENSE`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-task helpers for building Voxel-Plugin-flavoured PCG graphs. None of
|
|
3
|
+
* these are referenced by ue-mcp.plugin.yml directly - task files import them.
|
|
4
|
+
*
|
|
5
|
+
* The exact PCG node-type names depend on the Voxel Plugin version installed.
|
|
6
|
+
* They are intentionally string constants so they can be updated in one
|
|
7
|
+
* place when the plugin's exposed nodes shift.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VOXEL_NODES: {
|
|
10
|
+
/** Samples the active voxel volume in PCG space. */
|
|
11
|
+
readonly voxelSampler: "VoxelSampler";
|
|
12
|
+
/** Surface sampler that filters points to voxel-terrain surface positions. */
|
|
13
|
+
readonly surfaceSampler: "PCGSurfaceSampler";
|
|
14
|
+
/** Static-mesh spawner consuming the points from the surface sampler. */
|
|
15
|
+
readonly staticMeshSpawner: "PCGStaticMeshSpawner";
|
|
16
|
+
/** Stamp-spawner specific to Voxel Plugin's stamp asset format. */
|
|
17
|
+
readonly voxelStamp: "VoxelStamp";
|
|
18
|
+
/** Density filter for thinning out sampled points. */
|
|
19
|
+
readonly densityFilter: "PCGDensityFilter";
|
|
20
|
+
/** Transform-randomiser for scale/rotation jitter. */
|
|
21
|
+
readonly transformPoints: "PCGTransformPoints";
|
|
22
|
+
};
|
|
23
|
+
export type VoxelNodeKey = keyof typeof VOXEL_NODES;
|
|
24
|
+
/**
|
|
25
|
+
* Type-safe wrapper around `this.call('pcg.add_node', ...)`. Returns the
|
|
26
|
+
* caller-visible node id reported by the bridge, or throws so the task gets
|
|
27
|
+
* a meaningful error.
|
|
28
|
+
*/
|
|
29
|
+
export declare function addNode(task: unknown, graphPath: string, nodeKey: VoxelNodeKey): Promise<string>;
|
|
30
|
+
export declare function connectNodes(task: unknown, graphPath: string, sourceId: string, targetId: string): Promise<void>;
|
|
31
|
+
export declare function setNodeSettings(task: unknown, graphPath: string, nodeId: string, settings: Record<string, unknown>): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-task helpers for building Voxel-Plugin-flavoured PCG graphs. None of
|
|
3
|
+
* these are referenced by ue-mcp.plugin.yml directly - task files import them.
|
|
4
|
+
*
|
|
5
|
+
* The exact PCG node-type names depend on the Voxel Plugin version installed.
|
|
6
|
+
* They are intentionally string constants so they can be updated in one
|
|
7
|
+
* place when the plugin's exposed nodes shift.
|
|
8
|
+
*/
|
|
9
|
+
export const VOXEL_NODES = {
|
|
10
|
+
/** Samples the active voxel volume in PCG space. */
|
|
11
|
+
voxelSampler: "VoxelSampler",
|
|
12
|
+
/** Surface sampler that filters points to voxel-terrain surface positions. */
|
|
13
|
+
surfaceSampler: "PCGSurfaceSampler",
|
|
14
|
+
/** Static-mesh spawner consuming the points from the surface sampler. */
|
|
15
|
+
staticMeshSpawner: "PCGStaticMeshSpawner",
|
|
16
|
+
/** Stamp-spawner specific to Voxel Plugin's stamp asset format. */
|
|
17
|
+
voxelStamp: "VoxelStamp",
|
|
18
|
+
/** Density filter for thinning out sampled points. */
|
|
19
|
+
densityFilter: "PCGDensityFilter",
|
|
20
|
+
/** Transform-randomiser for scale/rotation jitter. */
|
|
21
|
+
transformPoints: "PCGTransformPoints",
|
|
22
|
+
};
|
|
23
|
+
function callable(task) {
|
|
24
|
+
return task;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Type-safe wrapper around `this.call('pcg.add_node', ...)`. Returns the
|
|
28
|
+
* caller-visible node id reported by the bridge, or throws so the task gets
|
|
29
|
+
* a meaningful error.
|
|
30
|
+
*/
|
|
31
|
+
export async function addNode(task, graphPath, nodeKey) {
|
|
32
|
+
const r = await callable(task).call("pcg.add_node", {
|
|
33
|
+
graphPath,
|
|
34
|
+
nodeType: VOXEL_NODES[nodeKey],
|
|
35
|
+
});
|
|
36
|
+
if (!r.success) {
|
|
37
|
+
throw new Error(`pcg.add_node ${nodeKey} failed: ${r.error?.message ?? "unknown"}`);
|
|
38
|
+
}
|
|
39
|
+
const id = (r.data?.nodeId ?? r.data?.id);
|
|
40
|
+
if (!id) {
|
|
41
|
+
throw new Error(`pcg.add_node ${nodeKey} did not return a node id`);
|
|
42
|
+
}
|
|
43
|
+
return id;
|
|
44
|
+
}
|
|
45
|
+
export async function connectNodes(task, graphPath, sourceId, targetId) {
|
|
46
|
+
const r = await callable(task).call("pcg.connect_nodes", {
|
|
47
|
+
graphPath,
|
|
48
|
+
sourceNodeId: sourceId,
|
|
49
|
+
targetNodeId: targetId,
|
|
50
|
+
});
|
|
51
|
+
if (!r.success) {
|
|
52
|
+
throw new Error(`pcg.connect_nodes failed: ${r.error?.message ?? "unknown"}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function setNodeSettings(task, graphPath, nodeId, settings) {
|
|
56
|
+
const r = await callable(task).call("pcg.set_node_settings", {
|
|
57
|
+
graphPath,
|
|
58
|
+
nodeId,
|
|
59
|
+
settings,
|
|
60
|
+
});
|
|
61
|
+
if (!r.success) {
|
|
62
|
+
throw new Error(`pcg.set_node_settings failed: ${r.error?.message ?? "unknown"}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=voxelGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voxelGraph.js","sourceRoot":"","sources":["../../src/shared/voxelGraph.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,oDAAoD;IACpD,YAAY,EAAE,cAAc;IAC5B,8EAA8E;IAC9E,cAAc,EAAE,mBAAmB;IACnC,yEAAyE;IACzE,iBAAiB,EAAE,sBAAsB;IACzC,mEAAmE;IACnE,UAAU,EAAE,YAAY;IACxB,sDAAsD;IACtD,aAAa,EAAE,kBAAkB;IACjC,sDAAsD;IACtD,eAAe,EAAE,oBAAoB;CAC7B,CAAC;AAaX,SAAS,QAAQ,CAAC,IAAa;IAC7B,OAAO,IAAmB,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAa,EACb,SAAiB,EACjB,OAAqB;IAErB,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE;QAClD,SAAS;QACT,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC;KAC/B,CAAC,CAAC;IACH,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,gBAAgB,OAAO,YAAY,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;IACtF,CAAC;IACD,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAuB,CAAC;IAChE,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,gBAAgB,OAAO,2BAA2B,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAa,EACb,SAAiB,EACjB,QAAgB,EAChB,QAAgB;IAEhB,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,EAAE;QACvD,SAAS;QACT,YAAY,EAAE,QAAQ;QACtB,YAAY,EAAE,QAAQ;KACvB,CAAC,CAAC;IACH,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAa,EACb,SAAiB,EACjB,MAAc,EACd,QAAiC;IAEjC,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,uBAAuB,EAAE;QAC3D,SAAS;QACT,MAAM;QACN,QAAQ;KACT,CAAC,CAAC;IACH,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;IACpF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseTask, type TaskResult } from "@db-lyon/flowkit";
|
|
2
|
+
interface Bounds {
|
|
3
|
+
min: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
z?: number;
|
|
7
|
+
};
|
|
8
|
+
max: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
z?: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
interface Options {
|
|
15
|
+
landscapeLabel: string;
|
|
16
|
+
bounds: Bounds;
|
|
17
|
+
/** Heightmap side length in samples. Default 1009 (UE landscape-friendly). */
|
|
18
|
+
resolution?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Bakes a region of a Voxel Plugin terrain to a Landscape heightmap.
|
|
22
|
+
*
|
|
23
|
+
* Pipeline:
|
|
24
|
+
* 1. Sample the voxel surface at a grid resolution by issuing Python through
|
|
25
|
+
* the editor (the C++ bridge has no direct Voxel Plugin API surface).
|
|
26
|
+
* 2. Pipe the resulting heightfield into `landscape.import_heightmap`.
|
|
27
|
+
*
|
|
28
|
+
* The Python step is the escape hatch documented in CLAUDE.md and is
|
|
29
|
+
* acceptable here because Voxel Plugin exposes no native PCG-side way to
|
|
30
|
+
* extract raw voxel heights into the engine's landscape format.
|
|
31
|
+
*/
|
|
32
|
+
export default class BakeHeightmap extends BaseTask<Options> {
|
|
33
|
+
get taskName(): string;
|
|
34
|
+
protected validate(): void;
|
|
35
|
+
execute(): Promise<TaskResult>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { BaseTask } from "@db-lyon/flowkit";
|
|
2
|
+
/**
|
|
3
|
+
* Bakes a region of a Voxel Plugin terrain to a Landscape heightmap.
|
|
4
|
+
*
|
|
5
|
+
* Pipeline:
|
|
6
|
+
* 1. Sample the voxel surface at a grid resolution by issuing Python through
|
|
7
|
+
* the editor (the C++ bridge has no direct Voxel Plugin API surface).
|
|
8
|
+
* 2. Pipe the resulting heightfield into `landscape.import_heightmap`.
|
|
9
|
+
*
|
|
10
|
+
* The Python step is the escape hatch documented in CLAUDE.md and is
|
|
11
|
+
* acceptable here because Voxel Plugin exposes no native PCG-side way to
|
|
12
|
+
* extract raw voxel heights into the engine's landscape format.
|
|
13
|
+
*/
|
|
14
|
+
export default class BakeHeightmap extends BaseTask {
|
|
15
|
+
get taskName() { return "voxel.bake_heightmap"; }
|
|
16
|
+
validate() {
|
|
17
|
+
if (!this.options.landscapeLabel)
|
|
18
|
+
throw new Error("landscapeLabel is required");
|
|
19
|
+
if (!this.options.bounds?.min || !this.options.bounds?.max) {
|
|
20
|
+
throw new Error("bounds.min and bounds.max are required");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async execute() {
|
|
24
|
+
const { landscapeLabel, bounds, resolution = 1009 } = this.options;
|
|
25
|
+
// 1. Sample voxel heights via Python. The script writes a raw uint16
|
|
26
|
+
// heightmap to the project's intermediate dir and returns its path.
|
|
27
|
+
const sample = await this.call("editor.execute_python", {
|
|
28
|
+
script: buildVoxelSampleScript(bounds, resolution),
|
|
29
|
+
});
|
|
30
|
+
if (!sample.success)
|
|
31
|
+
return sample;
|
|
32
|
+
const heightmapPath = (sample.data?.result ?? sample.data?.output ?? sample.data?.heightmapPath);
|
|
33
|
+
if (!heightmapPath) {
|
|
34
|
+
return {
|
|
35
|
+
success: false,
|
|
36
|
+
error: new Error("voxel sample script did not return a heightmap path"),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// 2. Import the heightmap into the target landscape actor.
|
|
40
|
+
const imported = await this.call("landscape.import_heightmap", {
|
|
41
|
+
actorLabel: landscapeLabel,
|
|
42
|
+
heightmapPath,
|
|
43
|
+
resolution,
|
|
44
|
+
});
|
|
45
|
+
if (!imported.success)
|
|
46
|
+
return imported;
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
data: {
|
|
50
|
+
landscapeLabel,
|
|
51
|
+
heightmapPath,
|
|
52
|
+
resolution,
|
|
53
|
+
bounds,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function buildVoxelSampleScript(bounds, resolution) {
|
|
59
|
+
// The script is kept in a single string so the bridge ships it verbatim to
|
|
60
|
+
// Python. It is intentionally defensive: any Voxel Plugin API the host
|
|
61
|
+
// installation may not have is caught and reported as a clear error rather
|
|
62
|
+
// than a traceback.
|
|
63
|
+
return `
|
|
64
|
+
import unreal, os, struct
|
|
65
|
+
|
|
66
|
+
bounds_min = (${bounds.min.x}, ${bounds.min.y})
|
|
67
|
+
bounds_max = (${bounds.max.x}, ${bounds.max.y})
|
|
68
|
+
res = ${resolution}
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
voxel_lib = unreal.VoxelBlueprintLibrary # exposed by Voxel Plugin at runtime
|
|
72
|
+
except AttributeError:
|
|
73
|
+
raise RuntimeError("Voxel Plugin not loaded - voxel.bake_heightmap requires the Voxel plugin")
|
|
74
|
+
|
|
75
|
+
dx = (bounds_max[0] - bounds_min[0]) / float(res - 1)
|
|
76
|
+
dy = (bounds_max[1] - bounds_min[1]) / float(res - 1)
|
|
77
|
+
|
|
78
|
+
samples = bytearray()
|
|
79
|
+
for j in range(res):
|
|
80
|
+
for i in range(res):
|
|
81
|
+
x = bounds_min[0] + i * dx
|
|
82
|
+
y = bounds_min[1] + j * dy
|
|
83
|
+
try:
|
|
84
|
+
h = voxel_lib.get_voxel_height_at(x, y)
|
|
85
|
+
except Exception:
|
|
86
|
+
h = 0.0
|
|
87
|
+
# Map [-32768, 32767] cm into uint16. Adjust per project needs.
|
|
88
|
+
sample = max(0, min(65535, int(h + 32768)))
|
|
89
|
+
samples += struct.pack("<H", sample)
|
|
90
|
+
|
|
91
|
+
out_dir = os.path.join(unreal.Paths.project_intermediate_dir(), "Voxel")
|
|
92
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
93
|
+
out_path = os.path.join(out_dir, "voxel_heightmap.r16")
|
|
94
|
+
with open(out_path, "wb") as f:
|
|
95
|
+
f.write(samples)
|
|
96
|
+
|
|
97
|
+
# The bridge serialises whatever we print as the script result.
|
|
98
|
+
print(out_path)
|
|
99
|
+
`.trim();
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=BakeHeightmap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BakeHeightmap.js","sourceRoot":"","sources":["../../src/tasks/BakeHeightmap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmB,MAAM,kBAAkB,CAAC;AAc7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,QAAiB;IAC1D,IAAI,QAAQ,KAAa,OAAO,sBAAsB,CAAC,CAAC,CAAC;IAE/C,QAAQ;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAChF,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAEnE,qEAAqE;QACrE,uEAAuE;QACvE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE;YACtD,MAAM,EAAE,sBAAsB,CAAC,MAAM,EAAE,UAAU,CAAC;SACnD,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC;QAEnC,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,aAAa,CAAuB,CAAC;QACvH,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,IAAI,KAAK,CAAC,qDAAqD,CAAC;aACxE,CAAC;QACJ,CAAC;QAED,2DAA2D;QAC3D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,4BAA4B,EAAE;YAC7D,UAAU,EAAE,cAAc;YAC1B,aAAa;YACb,UAAU;SACX,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAEvC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,cAAc;gBACd,aAAa;gBACb,UAAU;gBACV,MAAM;aACP;SACF,CAAC;IACJ,CAAC;CACF;AAED,SAAS,sBAAsB,CAAC,MAAc,EAAE,UAAkB;IAChE,2EAA2E;IAC3E,uEAAuE;IACvE,2EAA2E;IAC3E,oBAAoB;IACpB,OAAO;;;gBAGO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC;QACrC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BjB,CAAC,IAAI,EAAE,CAAC;AACT,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BaseTask, type TaskResult } from "@db-lyon/flowkit";
|
|
2
|
+
interface Options {
|
|
3
|
+
graphPath: string;
|
|
4
|
+
mesh: string;
|
|
5
|
+
density?: number;
|
|
6
|
+
minScale?: number;
|
|
7
|
+
maxScale?: number;
|
|
8
|
+
seed?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Builds a PCG graph that scatters static meshes on a Voxel Plugin
|
|
12
|
+
* terrain. Pipeline: VoxelSampler → SurfaceSampler → DensityFilter →
|
|
13
|
+
* TransformPoints → StaticMeshSpawner.
|
|
14
|
+
*
|
|
15
|
+
* Idempotency: if the graph already exists, the task assumes the caller
|
|
16
|
+
* wants a fresh build and will append nodes. Callers expecting strict
|
|
17
|
+
* idempotency should delete the graph first via `pcg(action="delete_graph")`
|
|
18
|
+
* (or skip the call entirely when no rebuild is needed).
|
|
19
|
+
*/
|
|
20
|
+
export default class ScatterMeshes extends BaseTask<Options> {
|
|
21
|
+
get taskName(): string;
|
|
22
|
+
protected validate(): void;
|
|
23
|
+
execute(): Promise<TaskResult>;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BaseTask } from "@db-lyon/flowkit";
|
|
2
|
+
import { addNode, connectNodes, setNodeSettings } from "../shared/voxelGraph.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a PCG graph that scatters static meshes on a Voxel Plugin
|
|
5
|
+
* terrain. Pipeline: VoxelSampler → SurfaceSampler → DensityFilter →
|
|
6
|
+
* TransformPoints → StaticMeshSpawner.
|
|
7
|
+
*
|
|
8
|
+
* Idempotency: if the graph already exists, the task assumes the caller
|
|
9
|
+
* wants a fresh build and will append nodes. Callers expecting strict
|
|
10
|
+
* idempotency should delete the graph first via `pcg(action="delete_graph")`
|
|
11
|
+
* (or skip the call entirely when no rebuild is needed).
|
|
12
|
+
*/
|
|
13
|
+
export default class ScatterMeshes extends BaseTask {
|
|
14
|
+
get taskName() { return "voxel.scatter_meshes"; }
|
|
15
|
+
validate() {
|
|
16
|
+
if (!this.options.graphPath)
|
|
17
|
+
throw new Error("graphPath is required");
|
|
18
|
+
if (!this.options.mesh)
|
|
19
|
+
throw new Error("mesh is required");
|
|
20
|
+
}
|
|
21
|
+
async execute() {
|
|
22
|
+
const { graphPath, mesh, density = 0.25, minScale = 0.8, maxScale = 1.4, seed = 1 } = this.options;
|
|
23
|
+
// Create the graph (no-op if pcg.create_graph rejects an existing one;
|
|
24
|
+
// we proceed regardless so add_node can land on a pre-existing graph).
|
|
25
|
+
await this.call("pcg.create_graph", { path: graphPath });
|
|
26
|
+
const sampler = await addNode(this, graphPath, "voxelSampler");
|
|
27
|
+
const surface = await addNode(this, graphPath, "surfaceSampler");
|
|
28
|
+
const density_ = await addNode(this, graphPath, "densityFilter");
|
|
29
|
+
const transform = await addNode(this, graphPath, "transformPoints");
|
|
30
|
+
const spawner = await addNode(this, graphPath, "staticMeshSpawner");
|
|
31
|
+
await setNodeSettings(this, graphPath, surface, { PointsPerSquaredMeter: density });
|
|
32
|
+
await setNodeSettings(this, graphPath, density_, { LowerBound: 0.0, UpperBound: 1.0, Seed: seed });
|
|
33
|
+
await setNodeSettings(this, graphPath, transform, { MinScale: minScale, MaxScale: maxScale, Seed: seed });
|
|
34
|
+
await setNodeSettings(this, graphPath, spawner, { StaticMesh: mesh });
|
|
35
|
+
await connectNodes(this, graphPath, sampler, surface);
|
|
36
|
+
await connectNodes(this, graphPath, surface, density_);
|
|
37
|
+
await connectNodes(this, graphPath, density_, transform);
|
|
38
|
+
await connectNodes(this, graphPath, transform, spawner);
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
data: {
|
|
42
|
+
graphPath,
|
|
43
|
+
nodes: { sampler, surface, density: density_, transform, spawner },
|
|
44
|
+
mesh,
|
|
45
|
+
},
|
|
46
|
+
// No automatic rollback - PCG node creation/connection has no inverse
|
|
47
|
+
// exposed by the bridge. Callers wanting safety should run inside a
|
|
48
|
+
// flow with git_snapshot enabled.
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=ScatterMeshes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScatterMeshes.js","sourceRoot":"","sources":["../../src/tasks/ScatterMeshes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmB,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAWjF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,QAAiB;IAC1D,IAAI,QAAQ,KAAa,OAAO,sBAAsB,CAAC,CAAC,CAAC;IAE/C,QAAQ;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,QAAQ,GAAG,GAAG,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAEnG,uEAAuE;QACvE,uEAAuE;QACvE,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAEzD,MAAM,OAAO,GAAK,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;QACjE,MAAM,OAAO,GAAK,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;QACnE,MAAM,QAAQ,GAAI,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QAClE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC;QACpE,MAAM,OAAO,GAAK,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;QAEtE,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,CAAC,CAAC;QACtF,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACpG,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1G,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAExE,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAI,OAAO,CAAC,CAAC;QACxD,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAI,QAAQ,CAAC,CAAC;QACzD,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAG,SAAS,CAAC,CAAC;QAC1D,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAExD,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,SAAS;gBACT,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE;gBAClE,IAAI;aACL;YACD,sEAAsE;YACtE,oEAAoE;YACpE,kCAAkC;SACnC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseTask, type TaskResult } from "@db-lyon/flowkit";
|
|
2
|
+
interface Options {
|
|
3
|
+
graphPath: string;
|
|
4
|
+
stampAsset: string;
|
|
5
|
+
count?: number;
|
|
6
|
+
radius?: number;
|
|
7
|
+
seed?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Builds a PCG graph that places voxel stamp assets across a terrain. Pipeline:
|
|
11
|
+
* VoxelSampler → SurfaceSampler → DensityFilter (thinning by `count`) →
|
|
12
|
+
* VoxelStamp.
|
|
13
|
+
*
|
|
14
|
+
* `count` is a soft cap - the density filter approximates it from the sampled
|
|
15
|
+
* point cloud, so the final placement count varies with the underlying voxel
|
|
16
|
+
* volume density.
|
|
17
|
+
*/
|
|
18
|
+
export default class SpawnStamps extends BaseTask<Options> {
|
|
19
|
+
get taskName(): string;
|
|
20
|
+
protected validate(): void;
|
|
21
|
+
execute(): Promise<TaskResult>;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { BaseTask } from "@db-lyon/flowkit";
|
|
2
|
+
import { addNode, connectNodes, setNodeSettings } from "../shared/voxelGraph.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds a PCG graph that places voxel stamp assets across a terrain. Pipeline:
|
|
5
|
+
* VoxelSampler → SurfaceSampler → DensityFilter (thinning by `count`) →
|
|
6
|
+
* VoxelStamp.
|
|
7
|
+
*
|
|
8
|
+
* `count` is a soft cap - the density filter approximates it from the sampled
|
|
9
|
+
* point cloud, so the final placement count varies with the underlying voxel
|
|
10
|
+
* volume density.
|
|
11
|
+
*/
|
|
12
|
+
export default class SpawnStamps extends BaseTask {
|
|
13
|
+
get taskName() { return "voxel.spawn_stamps"; }
|
|
14
|
+
validate() {
|
|
15
|
+
if (!this.options.graphPath)
|
|
16
|
+
throw new Error("graphPath is required");
|
|
17
|
+
if (!this.options.stampAsset)
|
|
18
|
+
throw new Error("stampAsset is required");
|
|
19
|
+
}
|
|
20
|
+
async execute() {
|
|
21
|
+
const { graphPath, stampAsset, count = 50, radius = 500, seed = 1 } = this.options;
|
|
22
|
+
await this.call("pcg.create_graph", { path: graphPath });
|
|
23
|
+
const sampler = await addNode(this, graphPath, "voxelSampler");
|
|
24
|
+
const surface = await addNode(this, graphPath, "surfaceSampler");
|
|
25
|
+
const density_ = await addNode(this, graphPath, "densityFilter");
|
|
26
|
+
const stamp = await addNode(this, graphPath, "voxelStamp");
|
|
27
|
+
// PointsPerSquaredMeter is approximated from count/radius² to give the
|
|
28
|
+
// surface sampler a sensible density for the desired stamp count.
|
|
29
|
+
const pointsPerSqM = Math.max(0.0001, count / (Math.PI * radius * radius));
|
|
30
|
+
await setNodeSettings(this, graphPath, surface, {
|
|
31
|
+
PointsPerSquaredMeter: pointsPerSqM,
|
|
32
|
+
Radius: radius,
|
|
33
|
+
Seed: seed,
|
|
34
|
+
});
|
|
35
|
+
await setNodeSettings(this, graphPath, density_, { LowerBound: 0.5, UpperBound: 1.0, Seed: seed });
|
|
36
|
+
await setNodeSettings(this, graphPath, stamp, { StampAsset: stampAsset });
|
|
37
|
+
await connectNodes(this, graphPath, sampler, surface);
|
|
38
|
+
await connectNodes(this, graphPath, surface, density_);
|
|
39
|
+
await connectNodes(this, graphPath, density_, stamp);
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
data: {
|
|
43
|
+
graphPath,
|
|
44
|
+
nodes: { sampler, surface, density: density_, stamp },
|
|
45
|
+
stampAsset,
|
|
46
|
+
countEstimate: count,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=SpawnStamps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpawnStamps.js","sourceRoot":"","sources":["../../src/tasks/SpawnStamps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAmB,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAUjF;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,QAAiB;IACxD,IAAI,QAAQ,KAAa,OAAO,oBAAoB,CAAC,CAAC,CAAC;IAE7C,QAAQ;QAChB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,GAAG,EAAE,EAAE,MAAM,GAAG,GAAG,EAAE,IAAI,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAEnF,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAEzD,MAAM,OAAO,GAAI,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;QAChE,MAAM,OAAO,GAAI,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;QAClE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QACjE,MAAM,KAAK,GAAM,MAAM,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;QAE9D,uEAAuE;QACvE,kEAAkE;QAClE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC;QAE3E,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE;YAC9C,qBAAqB,EAAE,YAAY;YACnC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QACH,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACnG,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAK,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;QAE7E,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAG,OAAO,CAAC,CAAC;QACvD,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAG,QAAQ,CAAC,CAAC;QACxD,MAAM,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAErD,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,SAAS;gBACT,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE;gBACrD,UAAU;gBACV,aAAa,EAAE,KAAK;aACrB;SACF,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Voxel Plugin - Landscape actions
|
|
2
|
+
|
|
3
|
+
This plugin contributes one landscape-side action for [Voxel Plugin](https://voxelplugin.com):
|
|
4
|
+
|
|
5
|
+
- `landscape(action="voxel_bake_heightmap", ...)` - bake a region of a voxel terrain into a standard UE Landscape heightmap.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
Use it when the user wants to convert a voxel terrain (or a region of it) into a standard Landscape actor for the parts of the workflow that need it: high-quality runtime LOD, foliage painting, landscape materials, etc.
|
|
10
|
+
|
|
11
|
+
The reverse conversion (Landscape → voxel) is not part of this plugin.
|
|
12
|
+
|
|
13
|
+
## Typical sequence
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
landscape(action="voxel_bake_heightmap",
|
|
17
|
+
landscapeLabel="Landscape",
|
|
18
|
+
bounds={ min: {x: 0, y: 0},
|
|
19
|
+
max: {x: 8064, y: 8064} },
|
|
20
|
+
resolution=1009)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The action samples voxel heights on a `resolution x resolution` grid spanning `bounds`, writes a raw uint16 heightmap to `Intermediate/Voxel/voxel_heightmap.r16`, then imports it into the target landscape via the built-in `landscape.import_heightmap` action.
|
|
24
|
+
|
|
25
|
+
## Parameters
|
|
26
|
+
|
|
27
|
+
- `landscapeLabel` (required) - the in-editor label of the target Landscape actor.
|
|
28
|
+
- `bounds` (required) - `{ min: {x, y}, max: {x, y} }` in world centimetres.
|
|
29
|
+
- `resolution` (optional, default 1009) - heightmap side length in samples. Use a UE-friendly value: 127, 253, 505, 1009, 2017, 4033, 8129.
|
|
30
|
+
|
|
31
|
+
## Notes
|
|
32
|
+
|
|
33
|
+
- The Python escape hatch is used here because Voxel Plugin exposes its voxel heights to Python but not to PCG or the C++ bridge directly. The script is defensive: if `VoxelBlueprintLibrary` is not loaded it returns a clear error rather than a traceback.
|
|
34
|
+
- Height mapping uses a centred uint16 range (offset +32768). If your voxel volume uses a different scale, adjust the script or wrap this action.
|
package/knowledge/pcg.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Voxel Plugin - PCG actions
|
|
2
|
+
|
|
3
|
+
This plugin contributes two PCG-side actions for [Voxel Plugin](https://voxelplugin.com) terrains:
|
|
4
|
+
|
|
5
|
+
- `pcg(action="voxel_scatter_meshes", ...)` - build a graph that scatters static meshes on a voxel terrain via Voxel Sampler.
|
|
6
|
+
- `pcg(action="voxel_spawn_stamps", ...)` - build a graph that places voxel stamp assets across a voxel terrain.
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
Reach for these when the user wants procedural placement on a **voxel** terrain. For standard UE Landscape, use the built-in PCG nodes directly.
|
|
11
|
+
|
|
12
|
+
## Typical sequence
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
pcg(action="create_graph", path="/Game/PCG/MyScatter") # if no graph exists
|
|
16
|
+
pcg(action="voxel_scatter_meshes",
|
|
17
|
+
graphPath="/Game/PCG/MyScatter",
|
|
18
|
+
mesh="/Game/Foliage/Rock01.Rock01",
|
|
19
|
+
density=0.25)
|
|
20
|
+
pcg(action="execute", graphPath="/Game/PCG/MyScatter") # materialise
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The full setup is also available as a flow:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
flow(action="run", flowName="voxel_scatter_setup", params={
|
|
27
|
+
graphPath: "/Game/PCG/MyScatter",
|
|
28
|
+
mesh: "/Game/Foliage/Rock01.Rock01"
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Parameters
|
|
33
|
+
|
|
34
|
+
`voxel_scatter_meshes`:
|
|
35
|
+
- `graphPath` (required) - asset path of the PCG graph to build (created if missing).
|
|
36
|
+
- `mesh` (required) - StaticMesh asset path to scatter.
|
|
37
|
+
- `density` (optional, default 0.25) - points per square metre at the surface sampler.
|
|
38
|
+
- `minScale` / `maxScale` (optional) - uniform scale jitter, default 0.8 .. 1.4.
|
|
39
|
+
- `seed` (optional) - deterministic seed for the density filter and transform jitter.
|
|
40
|
+
|
|
41
|
+
`voxel_spawn_stamps`:
|
|
42
|
+
- `graphPath` (required).
|
|
43
|
+
- `stampAsset` (required) - Voxel stamp asset path.
|
|
44
|
+
- `count` (optional, default 50) - target stamp count; approximated via density.
|
|
45
|
+
- `radius` (optional, default 500) - radius of the scatter region in cm.
|
|
46
|
+
- `seed` (optional).
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
The host project must have the Voxel Plugin enabled in its `.uproject` as `Voxel` (the `.uplugin` filename). The plugin installer warns at install time if `Voxel` is missing; the actions themselves will fail with a clear error if the runtime types aren't loaded.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ue-mcp-plugin-voxel-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Voxel Plugin actions for ue-mcp - voxel terrain scatter, stamps, and heightmap baking via PCG and Landscape.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"ue-mcp.plugin.yml",
|
|
10
|
+
"knowledge",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ue-mcp-plugin",
|
|
16
|
+
"unreal-engine",
|
|
17
|
+
"ue5",
|
|
18
|
+
"voxel",
|
|
19
|
+
"voxel-plugin",
|
|
20
|
+
"pcg",
|
|
21
|
+
"landscape"
|
|
22
|
+
],
|
|
23
|
+
"author": "David Lyon",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/db-lyon/ue-mcp-plugin-voxel-plugin.git"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@db-lyon/flowkit": "~0.5.2"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@db-lyon/flowkit": "~0.5.2",
|
|
34
|
+
"typescript": "^5.7.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
actionPrefix: voxel
|
|
2
|
+
minServerVersion: 1.0.15
|
|
3
|
+
uePluginDependency: Voxel
|
|
4
|
+
|
|
5
|
+
inject:
|
|
6
|
+
pcg:
|
|
7
|
+
scatter_meshes:
|
|
8
|
+
task: voxel.scatter_meshes
|
|
9
|
+
description: "Build a PCG graph that scatters static meshes on a voxel terrain via Voxel Sampler. Params: graphPath, mesh, density?, minScale?, maxScale?, seed?"
|
|
10
|
+
schema:
|
|
11
|
+
graphPath: { type: string, required: true }
|
|
12
|
+
mesh: { type: string, required: true }
|
|
13
|
+
density: { type: number }
|
|
14
|
+
minScale: { type: number }
|
|
15
|
+
maxScale: { type: number }
|
|
16
|
+
seed: { type: number }
|
|
17
|
+
spawn_stamps:
|
|
18
|
+
task: voxel.spawn_stamps
|
|
19
|
+
description: "Build a PCG graph that places voxel stamp assets on a voxel terrain. Params: graphPath, stampAsset, count?, radius?, seed?"
|
|
20
|
+
schema:
|
|
21
|
+
graphPath: { type: string, required: true }
|
|
22
|
+
stampAsset: { type: string, required: true }
|
|
23
|
+
count: { type: number }
|
|
24
|
+
radius: { type: number }
|
|
25
|
+
seed: { type: number }
|
|
26
|
+
|
|
27
|
+
landscape:
|
|
28
|
+
bake_heightmap:
|
|
29
|
+
task: voxel.bake_heightmap
|
|
30
|
+
description: "Bake a region of a voxel terrain into a standard Landscape heightmap. Params: landscapeLabel, bounds, resolution?"
|
|
31
|
+
schema:
|
|
32
|
+
landscapeLabel: { type: string, required: true }
|
|
33
|
+
bounds: { type: object, required: true }
|
|
34
|
+
resolution: { type: number }
|
|
35
|
+
|
|
36
|
+
knowledge:
|
|
37
|
+
pcg: knowledge/pcg.md
|
|
38
|
+
landscape: knowledge/landscape.md
|
|
39
|
+
|
|
40
|
+
tasks:
|
|
41
|
+
voxel.scatter_meshes:
|
|
42
|
+
class_path: tasks/ScatterMeshes
|
|
43
|
+
description: "Build the PCG graph for voxel-terrain mesh scatter"
|
|
44
|
+
voxel.spawn_stamps:
|
|
45
|
+
class_path: tasks/SpawnStamps
|
|
46
|
+
description: "Build the PCG graph for voxel stamps"
|
|
47
|
+
voxel.bake_heightmap:
|
|
48
|
+
class_path: tasks/BakeHeightmap
|
|
49
|
+
description: "Bake a voxel-terrain region into a Landscape heightmap"
|
|
50
|
+
|
|
51
|
+
flows:
|
|
52
|
+
voxel_scatter_setup:
|
|
53
|
+
description: "Create a PCG mesh-scatter graph on a voxel terrain and execute it"
|
|
54
|
+
rollback_on_failure: true
|
|
55
|
+
steps:
|
|
56
|
+
1: { task: voxel.scatter_meshes }
|
|
57
|
+
2: { task: pcg.execute }
|
|
58
|
+
|
|
59
|
+
voxel_stamps_setup:
|
|
60
|
+
description: "Create a PCG voxel-stamp graph and execute it"
|
|
61
|
+
rollback_on_failure: true
|
|
62
|
+
steps:
|
|
63
|
+
1: { task: voxel.spawn_stamps }
|
|
64
|
+
2: { task: pcg.execute }
|