Add three art pilots and derive chassis envelopes from final models (#115 #114)

Keep authored JSON separate from generated output. Export rotated model bounds for SDK-free navigation and check grazing voxel corners. Include unretouched in-game before/after captures and explicit art/performance limits; pilots are an intermediate pass, not completion of the full asset set.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
2026-11-02 21:33:59 +03:00
parent 4a3500fdc0
commit 5620347ffe
39 changed files with 41310 additions and 3737 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -5,9 +5,8 @@
"flecktarn": "airdef:block/flecktarn",
"radar_face": "airdef:block/radar_face",
"metal": "airdef:block/metal",
"mk_de": "airdef:block/mk_de",
"bare_antenna": "airdef:block/bare_antenna",
"dark_metal": "airdef:block/dark_metal",
"mk_de": "airdef:block/mk_de",
"particle": "airdef:block/flecktarn_s21"
},
"elements": [
@@ -15,11 +14,11 @@
"from": [
-4.0,
0.0,
-6.0
-5.0
],
"to": [
20.0,
12.0,
5.0,
16.0
],
"faces": {
@@ -28,7 +27,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn_s21"
},
@@ -37,7 +36,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn_s21"
},
@@ -46,7 +45,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn"
},
@@ -55,7 +54,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn"
},
@@ -81,14 +80,14 @@
},
{
"from": [
-2.0,
12.0,
-2.0
-2.5,
5.0,
-5.0
],
"to": [
18.0,
14.0,
14.0
18.5,
12.0,
15.0
],
"faces": {
"north": {
@@ -96,7 +95,7 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn"
},
@@ -105,7 +104,7 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn"
},
@@ -114,7 +113,7 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn"
},
@@ -123,7 +122,75 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn"
}
}
},
{
"from": [
-2.0,
12.0,
-2.0
],
"to": [
18.0,
13.0,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
@@ -218,7 +285,7 @@
{
"from": [
7.0,
14.0,
13.0,
10.0
],
"to": [
@@ -232,7 +299,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal"
},
@@ -241,7 +308,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal"
},
@@ -250,7 +317,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal"
},
@@ -259,7 +326,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal"
},
@@ -285,150 +352,168 @@
},
{
"from": [
20.0,
3.0,
-5.5
-4.7,
4.8,
-4.0
],
"to": [
20.119999999999997,
6.5,
1.5
-2.5,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.5
2.2,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
0.25,
3.5
2.2,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
0.25,
7.0
2.2,
16
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
0.25,
7.0
2.2,
16
],
"texture": "#mk_de"
"texture": "#flecktarn"
}
},
"rotation": {
"angle": 22.5,
"axis": "z",
"origin": [
-3.6,
7.75,
5.0
]
}
},
{
"from": [
-4.119999999999997,
3.0,
-5.5
18.5,
4.8,
-4.0
],
"to": [
-4.0,
6.5,
1.5
20.7,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.5
2.1999999999999993,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
0.25,
3.5
2.1999999999999993,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
0.25,
7.0
2.1999999999999993,
16
],
"texture": "#mk_de"
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
0.25,
7.0
2.1999999999999993,
16
],
"texture": "#mk_de"
"texture": "#flecktarn"
}
},
"rotation": {
"angle": -22.5,
"axis": "z",
"origin": [
19.6,
7.75,
5.0
]
}
},
{
"from": [
5.5,
14.0,
0.0,
13.05,
2.0
],
"to": [
10.5,
14.8,
7.0
5.0,
13.7,
8.0
],
"faces": {
"north": {
@@ -436,143 +521,75 @@
0,
0,
5.0,
0.8000000000000007
0.6499999999999986
],
"texture": "#metal"
"texture": "#dark_metal"
},
"south": {
"uv": [
0,
0,
5.0,
0.8000000000000007
0.6499999999999986
],
"texture": "#metal"
"texture": "#dark_metal"
},
"east": {
"uv": [
0,
0,
5.0,
0.8000000000000007
6.0,
0.6499999999999986
],
"texture": "#metal"
"texture": "#dark_metal"
},
"west": {
"uv": [
0,
0,
5.0,
0.8000000000000007
6.0,
0.6499999999999986
],
"texture": "#metal"
"texture": "#dark_metal"
},
"up": {
"uv": [
0,
0,
5.0,
5.0
6.0
],
"texture": "#metal"
"texture": "#dark_metal"
},
"down": {
"uv": [
0,
0,
5.0,
5.0
6.0
],
"texture": "#metal"
"texture": "#dark_metal"
}
}
},
{
"from": [
2.0,
14.0,
5.0
],
"to": [
3.0,
16.0,
6.0
],
"faces": {
"north": {
"uv": [
0,
0,
1.0,
2.0
],
"texture": "#bare_antenna"
},
"south": {
"uv": [
0,
0,
1.0,
2.0
],
"texture": "#bare_antenna"
},
"east": {
"uv": [
0,
0,
1.0,
2.0
],
"texture": "#bare_antenna"
},
"west": {
"uv": [
0,
0,
1.0,
2.0
],
"texture": "#bare_antenna"
},
"up": {
"uv": [
0,
0,
1.0,
1.0
],
"texture": "#bare_antenna"
},
"down": {
"uv": [
0,
0,
1.0,
1.0
],
"texture": "#bare_antenna"
}
}
},
{
"from": [
-4.800000000000001,
7.0,
-5.0
],
"to": [
-4.0,
10.0,
-2.0
13.05,
2.0
],
"to": [
15.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
@@ -580,8 +597,8 @@
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
@@ -589,8 +606,8 @@
"uv": [
0,
0,
3.0,
3.0
6.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
@@ -598,8 +615,8 @@
"uv": [
0,
0,
3.0,
3.0
6.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
@@ -607,8 +624,8 @@
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
6.0
],
"texture": "#dark_metal"
},
@@ -616,8 +633,8 @@
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
6.0
],
"texture": "#dark_metal"
}
@@ -625,207 +642,139 @@
},
{
"from": [
20.0,
7.0,
-5.0
18.5,
6.0,
-4.0
],
"to": [
20.8,
10.0,
-2.0
18.619999999999997,
9.0,
2.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
0.25,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"south": {
"uv": [
0,
0,
0.8000000000000007,
0.25,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"east": {
"uv": [
0,
0,
3.0,
6.0,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"west": {
"uv": [
0,
0,
3.0,
6.0,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"up": {
"uv": [
0,
0,
0.8000000000000007,
3.0
0.25,
6.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"down": {
"uv": [
0,
0,
0.8000000000000007,
3.0
0.25,
6.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
}
}
},
{
"from": [
-4.800000000000001,
7.0,
-1.5
-2.6199999999999974,
6.0,
-4.0
],
"to": [
-4.0,
10.0,
0.5
-2.5,
9.0,
2.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
0.25,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"south": {
"uv": [
0,
0,
0.8000000000000007,
0.25,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"east": {
"uv": [
0,
0,
2.0,
6.0,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"west": {
"uv": [
0,
0,
2.0,
6.0,
3.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"up": {
"uv": [
0,
0,
0.8000000000000007,
2.0
0.25,
6.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
},
"down": {
"uv": [
0,
0,
0.8000000000000007,
2.0
0.25,
6.0
],
"texture": "#dark_metal"
}
}
},
{
"from": [
20.0,
7.0,
-1.5
],
"to": [
20.8,
10.0,
0.5
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
3.0
],
"texture": "#dark_metal"
},
"south": {
"uv": [
0,
0,
0.8000000000000007,
3.0
],
"texture": "#dark_metal"
},
"east": {
"uv": [
0,
0,
2.0,
3.0
],
"texture": "#dark_metal"
},
"west": {
"uv": [
0,
0,
2.0,
3.0
],
"texture": "#dark_metal"
},
"up": {
"uv": [
0,
0,
0.8000000000000007,
2.0
],
"texture": "#dark_metal"
},
"down": {
"uv": [
0,
0,
0.8000000000000007,
2.0
],
"texture": "#dark_metal"
"texture": "#mk_de"
}
}
}
]
}
}
@@ -5,9 +5,8 @@
"flecktarn_b": "airdef:block/flecktarn_b",
"radar_face_b": "airdef:block/radar_face_b",
"metal_b": "airdef:block/metal_b",
"mk_de_b": "airdef:block/mk_de_b",
"bare_antenna_b": "airdef:block/bare_antenna_b",
"dark_metal_b": "airdef:block/dark_metal_b",
"mk_de_b": "airdef:block/mk_de_b",
"particle": "airdef:block/flecktarn_s21_b"
},
"elements": [
@@ -15,11 +14,11 @@
"from": [
-4.0,
0.0,
-6.0
-5.0
],
"to": [
20.0,
12.0,
5.0,
16.0
],
"faces": {
@@ -28,7 +27,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn_s21_b"
},
@@ -37,7 +36,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn_s21_b"
},
@@ -46,7 +45,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn_b"
},
@@ -55,7 +54,7 @@
0,
0,
16,
12.0
5.0
],
"texture": "#flecktarn_b"
},
@@ -81,14 +80,14 @@
},
{
"from": [
-2.0,
12.0,
-2.0
-2.5,
5.0,
-5.0
],
"to": [
18.0,
14.0,
14.0
18.5,
12.0,
15.0
],
"faces": {
"north": {
@@ -96,7 +95,7 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn_b"
},
@@ -105,7 +104,7 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn_b"
},
@@ -114,7 +113,7 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn_b"
},
@@ -123,7 +122,75 @@
0,
0,
16,
2.0
7.0
],
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_b"
}
}
},
{
"from": [
-2.0,
12.0,
-2.0
],
"to": [
18.0,
13.0,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
@@ -218,7 +285,7 @@
{
"from": [
7.0,
14.0,
13.0,
10.0
],
"to": [
@@ -232,7 +299,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal_b"
},
@@ -241,7 +308,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal_b"
},
@@ -250,7 +317,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal_b"
},
@@ -259,7 +326,7 @@
0,
0,
2.0,
4.0
5.0
],
"texture": "#metal_b"
},
@@ -285,226 +352,244 @@
},
{
"from": [
20.0,
3.0,
-5.5
-4.7,
4.8,
-4.0
],
"to": [
20.119999999999997,
6.5,
1.5
-2.5,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.5
2.2,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
0.25,
3.5
2.2,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
0.25,
7.0
2.2,
16
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
0.25,
7.0
2.2,
16
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
}
},
"rotation": {
"angle": 22.5,
"axis": "z",
"origin": [
-3.6,
7.75,
5.0
]
}
},
{
"from": [
-4.119999999999997,
3.0,
-5.5
18.5,
4.8,
-4.0
],
"to": [
-4.0,
6.5,
1.5
20.7,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.5
2.1999999999999993,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
0.25,
3.5
2.1999999999999993,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
7.0,
3.5
16,
5.8999999999999995
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
0.25,
7.0
2.1999999999999993,
16
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
0.25,
7.0
2.1999999999999993,
16
],
"texture": "#mk_de_b"
"texture": "#flecktarn_b"
}
},
"rotation": {
"angle": -22.5,
"axis": "z",
"origin": [
19.6,
7.75,
5.0
]
}
},
{
"from": [
2.0,
14.0,
5.0
0.0,
13.05,
2.0
],
"to": [
3.0,
16.0,
6.0
5.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
1.0,
2.0
5.0,
0.6499999999999986
],
"texture": "#bare_antenna_b"
"texture": "#dark_metal_b"
},
"south": {
"uv": [
0,
0,
1.0,
2.0
5.0,
0.6499999999999986
],
"texture": "#bare_antenna_b"
"texture": "#dark_metal_b"
},
"east": {
"uv": [
0,
0,
1.0,
2.0
6.0,
0.6499999999999986
],
"texture": "#bare_antenna_b"
"texture": "#dark_metal_b"
},
"west": {
"uv": [
0,
0,
1.0,
2.0
6.0,
0.6499999999999986
],
"texture": "#bare_antenna_b"
"texture": "#dark_metal_b"
},
"up": {
"uv": [
0,
0,
1.0,
1.0
5.0,
6.0
],
"texture": "#bare_antenna_b"
"texture": "#dark_metal_b"
},
"down": {
"uv": [
0,
0,
1.0,
1.0
5.0,
6.0
],
"texture": "#bare_antenna_b"
"texture": "#dark_metal_b"
}
}
},
{
"from": [
-4.800000000000001,
7.0,
-5.0
],
"to": [
-4.0,
10.0,
-2.0
13.05,
2.0
],
"to": [
15.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
@@ -512,8 +597,8 @@
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
@@ -521,8 +606,8 @@
"uv": [
0,
0,
3.0,
3.0
6.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
@@ -530,8 +615,8 @@
"uv": [
0,
0,
3.0,
3.0
6.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
@@ -539,8 +624,8 @@
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
6.0
],
"texture": "#dark_metal_b"
},
@@ -548,8 +633,8 @@
"uv": [
0,
0,
0.8000000000000007,
3.0
5.0,
6.0
],
"texture": "#dark_metal_b"
}
@@ -557,139 +642,71 @@
},
{
"from": [
20.0,
7.0,
-5.0
-2.6199999999999974,
6.0,
-4.0
],
"to": [
20.8,
10.0,
-2.0
-2.5,
9.0,
2.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
0.25,
3.0
],
"texture": "#dark_metal_b"
"texture": "#mk_de_b"
},
"south": {
"uv": [
0,
0,
0.8000000000000007,
0.25,
3.0
],
"texture": "#dark_metal_b"
"texture": "#mk_de_b"
},
"east": {
"uv": [
0,
0,
3.0,
6.0,
3.0
],
"texture": "#dark_metal_b"
"texture": "#mk_de_b"
},
"west": {
"uv": [
0,
0,
3.0,
6.0,
3.0
],
"texture": "#dark_metal_b"
"texture": "#mk_de_b"
},
"up": {
"uv": [
0,
0,
0.8000000000000007,
3.0
0.25,
6.0
],
"texture": "#dark_metal_b"
"texture": "#mk_de_b"
},
"down": {
"uv": [
0,
0,
0.8000000000000007,
3.0
0.25,
6.0
],
"texture": "#dark_metal_b"
}
}
},
{
"from": [
-4.800000000000001,
7.0,
-1.5
],
"to": [
-4.0,
10.0,
0.5
],
"faces": {
"north": {
"uv": [
0,
0,
0.8000000000000007,
3.0
],
"texture": "#dark_metal_b"
},
"south": {
"uv": [
0,
0,
0.8000000000000007,
3.0
],
"texture": "#dark_metal_b"
},
"east": {
"uv": [
0,
0,
2.0,
3.0
],
"texture": "#dark_metal_b"
},
"west": {
"uv": [
0,
0,
2.0,
3.0
],
"texture": "#dark_metal_b"
},
"up": {
"uv": [
0,
0,
0.8000000000000007,
2.0
],
"texture": "#dark_metal_b"
},
"down": {
"uv": [
0,
0,
0.8000000000000007,
2.0
],
"texture": "#dark_metal_b"
"texture": "#mk_de_b"
}
}
}
]
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -154,10 +154,10 @@ fd5ca8c85d054580e6f95cb88bfed6216c9b41ddab9efec2eb4a642ad408c17c assets/airdef/
9fa54aaabfc0dd1b6af4fdd1fd4ac9a4248c4837801a7d74e441627f565a18fa assets/airdef/models/block/gepard_dish.json
993b426346250e248abea70a461a7e9343d264585997adee252d8f803d635acd assets/airdef/models/block/gepard_guns.json
9f5b572110c02fbae42a1aff8db716094d198b209d19dfc2e6a72430378fc179 assets/airdef/models/block/gepard_guns_burnt.json
288029fc93a84ee5d68605d061bde9fdb31b759a4a17766bb8df008beb9c2064 assets/airdef/models/block/gepard_hull.json
743dba3635ae9683e744c1691315a104e48e10011cfbe6e74565eecd6ce7dfcb assets/airdef/models/block/gepard_hull_burnt.json
38ae7a16ba13e5ac0cff7f8f1856e24b512f4e68bff7b71297b3d2838333fc2a assets/airdef/models/block/gepard_turret.json
cf464948fd36c74cf6f49b10f4f2e0e4b9e52b9d8324c27302e8e1c2ae8ae73f assets/airdef/models/block/gepard_turret_burnt.json
b1ee075e7ec6c4d044ae45ae488995371e6128fa5d38bdc6babdbae9f746a752 assets/airdef/models/block/gepard_hull.json
37179a4c80b8e440c9be9e94e28f3d4852af77831aca0e1d17f6db22dec43905 assets/airdef/models/block/gepard_hull_burnt.json
d2da35d3023db04251a73030f7baca7f448b2a341315c1a3d9a7d9ec8dfb5c6d assets/airdef/models/block/gepard_turret.json
0d2f0217c9e9e9d0305c990576f86ac5665738db5eead729750836e9e371aa85 assets/airdef/models/block/gepard_turret_burnt.json
d43f9cda780b1821a6e778555a1be5bd112a1d9655ff96d35acebec61cf11e0b assets/airdef/models/block/geran3.json
83cf0a42e59f6aee1039925f849398960334f2545558409e7ccd7173ac841d73 assets/airdef/models/block/geran3_flame.json
ac93e872743844465f0688162dddd7433352e8710612dd391a1e92b7f36e6b0d assets/airdef/models/block/gerbera.json
@@ -203,10 +203,10 @@ b0ae344fbad3b982e7821cd1b26f9922e2a5713b38107521ddc5a5b2abd9e39c assets/airdef/
088f42bb17f4d6099c16adf6cc0426b6d04ff9a43abdf125476a3571e924fcbd assets/airdef/models/block/radar_truck.json
4cd60dfb1e598efbd127894b0ad1b1737569dfc0fc5c9b193034f9125daa9c15 assets/airdef/models/block/radar_truck_burnt.json
f102765390762d45954b26405b6acdde845df7f0a319bac53d25508e768957b5 assets/airdef/models/block/reentry.json
0a27047ed66a18b513c5113e4b4ba1d8667ac75d36f661e58415a9d6870c0bc9 assets/airdef/models/block/s400_chassis.json
b86ccc0a31dc63239b598542070173ebbc6d354b3d8dec880c62ea192d6ea9f7 assets/airdef/models/block/s400_chassis_burnt.json
0107a6689048c7267ea1c68713593026fa495cb33443a6b1aab815a17602fe81 assets/airdef/models/block/s400_tubes.json
6e4a35843d29bba30b9969af7ed9a60ad05ea4e4b9fde0507ea6ba6dcb9d7dd7 assets/airdef/models/block/s400_tubes_burnt.json
87085a7ac475dec2abcd5b1ff6443e2847614726219ca522b199bc70a5c10f2e assets/airdef/models/block/s400_chassis.json
f10710d0387edc746380287edbb987341cb88c95d974f89a48b19543cef737dc assets/airdef/models/block/s400_chassis_burnt.json
7b2cac0485b4242534e99bbfc0406ee393d508ca971d4d88caf3f13892b473c1 assets/airdef/models/block/s400_tubes.json
973e02b2058c0b3d59c156f2ac94fbba4ac90a97736e8a4cc9be177b5e0f19a9 assets/airdef/models/block/s400_tubes_burnt.json
088f42bb17f4d6099c16adf6cc0426b6d04ff9a43abdf125476a3571e924fcbd assets/airdef/models/block/s500_chassis.json
3e35ed19ea22c8309ac7c0ccc9c12e31b2387de97c6b72ba519bae34f681c58c assets/airdef/models/block/s500_chassis_burnt.json
6ce00881e2a5107874e82e94a243fcd95c3eb6e9a24e8839c11227c8e330485f assets/airdef/models/block/s500_tubes.json
@@ -215,7 +215,7 @@ b86ccc0a31dc63239b598542070173ebbc6d354b3d8dec880c62ea192d6ea9f7 assets/airdef/
0e3cbae5029377886325aedc43705d9975cb1dc889a37613b92a97bdf36b4db7 assets/airdef/models/block/searchlight_burnt.json
2d790e8845598e8cf7ae577492632ef52e075d590011e66fa8ab686096cda313 assets/airdef/models/block/searchlight_head.json
f0f9ec6cefc0750e4d6cb330c21df8c2bcf02b1b7408f90f40ebb35a572a13cb assets/airdef/models/block/searchlight_head_burnt.json
b2e28d73f50b238f6bc7d6dae560695e70ae8b843f6853f9e468bf8d2b371e6c assets/airdef/models/block/shahed.json
0d9aa1bc000619e0151c2263e847c81967ab1b7662464525cd42118f5d1d82f4 assets/airdef/models/block/shahed.json
d5b9ed76b1744d0c93a809b6a4c16bc3dfa7ff4784e3cb9968aa36b12fc30484 assets/airdef/models/block/shahed_prop.json
465e6aa2a8ac3c563b1e546fc844a0520fc89f3ce528d49f193aa6c21e19308f assets/airdef/models/block/target.json
69c5c103c228abe6162f2e1252139689b768ff8dd3f835f8f79ce7d324c3f124 assets/airdef/models/block/tor_chassis.json
@@ -570,3 +570,4 @@ cc0877f40434365b45b03429222926aa0cf135e504017311be53263fd27c5f8f data/airdef/ho
2a3912a7c9d5bb8025b1ac16a1a1d6b5eff0360bb3184e810d319e498255bf97 data/airdef/horizon/item/launch_zircon.json
2a3912a7c9d5bb8025b1ac16a1a1d6b5eff0360bb3184e810d319e498255bf97 data/airdef/horizon/item/mover.json
2a3912a7c9d5bb8025b1ac16a1a1d6b5eff0360bb3184e810d319e498255bf97 data/airdef/horizon/item/remover.json
8182bfca0dc64498eb620ffafe33c0e773d633f126f2194459a97f495ffe2ba1 src/sim/generated_bounds.rs
+6 -2
View File
@@ -7,5 +7,9 @@ copies this tree into its temporary staging tree, validates the merged runtime a
the generated manifest. Do not edit generated files under `airdef-mod/assets/` directly; an untracked or
hand-edited output is treated as a conflict and left untouched.
This directory is intentionally kept separate from runtime and generated output. It currently contains no
asset payloads.
This directory is intentionally kept separate from runtime and generated output. The initial art pilots are
S-400 chassis/canisters, Gepard hull/turret and Shahed, including burnt variants for the ground vehicles.
The JSON is editable directly. `python tools/pilot_models.py` is an optional authoring helper that overwrites
only those named pilot JSONs; the normal regeneration never invokes it. After editing, run
`python tools/gen_models.py` to validate and install the authored models. `generated_bounds.rs` is derived
from the final staged geometry, including overrides and element rotation, for chassis navigation.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,780 @@
{
"ambientocclusion": false,
"textures": {
"flecktarn_s21": "airdef:block/flecktarn_s21",
"flecktarn": "airdef:block/flecktarn",
"radar_face": "airdef:block/radar_face",
"metal": "airdef:block/metal",
"dark_metal": "airdef:block/dark_metal",
"mk_de": "airdef:block/mk_de",
"particle": "airdef:block/flecktarn_s21"
},
"elements": [
{
"from": [
-4.0,
0.0,
-5.0
],
"to": [
20.0,
5.0,
16.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn_s21"
},
"south": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn_s21"
},
"east": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_s21"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_s21"
}
}
},
{
"from": [
-2.5,
5.0,
-5.0
],
"to": [
18.5,
12.0,
15.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn"
}
}
},
{
"from": [
-2.0,
12.0,
-2.0
],
"to": [
18.0,
13.0,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn"
}
}
},
{
"from": [
3.0,
4.0,
-10.0
],
"to": [
13.0,
12.0,
-6.0
],
"faces": {
"north": {
"uv": [
0,
0,
10.0,
8.0
],
"texture": "#radar_face"
},
"south": {
"uv": [
0,
0,
10.0,
8.0
],
"texture": "#radar_face"
},
"east": {
"uv": [
0,
0,
4.0,
8.0
],
"texture": "#radar_face"
},
"west": {
"uv": [
0,
0,
4.0,
8.0
],
"texture": "#radar_face"
},
"up": {
"uv": [
0,
0,
10.0,
4.0
],
"texture": "#radar_face"
},
"down": {
"uv": [
0,
0,
10.0,
4.0
],
"texture": "#radar_face"
}
}
},
{
"from": [
7.0,
13.0,
10.0
],
"to": [
9.0,
18.0,
12.0
],
"faces": {
"north": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal"
},
"south": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal"
},
"east": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal"
},
"west": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal"
},
"up": {
"uv": [
0,
0,
2.0,
2.0
],
"texture": "#metal"
},
"down": {
"uv": [
0,
0,
2.0,
2.0
],
"texture": "#metal"
}
}
},
{
"from": [
-4.7,
4.8,
-4.0
],
"to": [
-2.5,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
2.2,
5.8999999999999995
],
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
2.2,
5.8999999999999995
],
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
2.2,
16
],
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
2.2,
16
],
"texture": "#flecktarn"
}
},
"rotation": {
"angle": 22.5,
"axis": "z",
"origin": [
-3.6,
7.75,
5.0
]
}
},
{
"from": [
18.5,
4.8,
-4.0
],
"to": [
20.7,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
2.1999999999999993,
5.8999999999999995
],
"texture": "#flecktarn"
},
"south": {
"uv": [
0,
0,
2.1999999999999993,
5.8999999999999995
],
"texture": "#flecktarn"
},
"east": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn"
},
"west": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn"
},
"up": {
"uv": [
0,
0,
2.1999999999999993,
16
],
"texture": "#flecktarn"
},
"down": {
"uv": [
0,
0,
2.1999999999999993,
16
],
"texture": "#flecktarn"
}
},
"rotation": {
"angle": -22.5,
"axis": "z",
"origin": [
19.6,
7.75,
5.0
]
}
},
{
"from": [
0.0,
13.05,
2.0
],
"to": [
5.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"south": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"east": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"west": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"up": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal"
},
"down": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal"
}
}
},
{
"from": [
10.0,
13.05,
2.0
],
"to": [
15.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"south": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"east": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"west": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal"
},
"up": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal"
},
"down": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal"
}
}
},
{
"from": [
18.5,
6.0,
-4.0
],
"to": [
18.619999999999997,
9.0,
2.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.0
],
"texture": "#mk_de"
},
"south": {
"uv": [
0,
0,
0.25,
3.0
],
"texture": "#mk_de"
},
"east": {
"uv": [
0,
0,
6.0,
3.0
],
"texture": "#mk_de"
},
"west": {
"uv": [
0,
0,
6.0,
3.0
],
"texture": "#mk_de"
},
"up": {
"uv": [
0,
0,
0.25,
6.0
],
"texture": "#mk_de"
},
"down": {
"uv": [
0,
0,
0.25,
6.0
],
"texture": "#mk_de"
}
}
},
{
"from": [
-2.6199999999999974,
6.0,
-4.0
],
"to": [
-2.5,
9.0,
2.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.0
],
"texture": "#mk_de"
},
"south": {
"uv": [
0,
0,
0.25,
3.0
],
"texture": "#mk_de"
},
"east": {
"uv": [
0,
0,
6.0,
3.0
],
"texture": "#mk_de"
},
"west": {
"uv": [
0,
0,
6.0,
3.0
],
"texture": "#mk_de"
},
"up": {
"uv": [
0,
0,
0.25,
6.0
],
"texture": "#mk_de"
},
"down": {
"uv": [
0,
0,
0.25,
6.0
],
"texture": "#mk_de"
}
}
}
]
}
@@ -0,0 +1,712 @@
{
"ambientocclusion": false,
"textures": {
"flecktarn_s21_b": "airdef:block/flecktarn_s21_b",
"flecktarn_b": "airdef:block/flecktarn_b",
"radar_face_b": "airdef:block/radar_face_b",
"metal_b": "airdef:block/metal_b",
"dark_metal_b": "airdef:block/dark_metal_b",
"mk_de_b": "airdef:block/mk_de_b",
"particle": "airdef:block/flecktarn_s21_b"
},
"elements": [
{
"from": [
-4.0,
0.0,
-5.0
],
"to": [
20.0,
5.0,
16.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn_s21_b"
},
"south": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn_s21_b"
},
"east": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
16,
5.0
],
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_s21_b"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_s21_b"
}
}
},
{
"from": [
-2.5,
5.0,
-5.0
],
"to": [
18.5,
12.0,
15.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
16,
7.0
],
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_b"
}
}
},
{
"from": [
-2.0,
12.0,
-2.0
],
"to": [
18.0,
13.0,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
16,
1.0
],
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
16,
16
],
"texture": "#flecktarn_b"
}
}
},
{
"from": [
3.0,
4.0,
-10.0
],
"to": [
13.0,
12.0,
-6.0
],
"faces": {
"north": {
"uv": [
0,
0,
10.0,
8.0
],
"texture": "#radar_face_b"
},
"south": {
"uv": [
0,
0,
10.0,
8.0
],
"texture": "#radar_face_b"
},
"east": {
"uv": [
0,
0,
4.0,
8.0
],
"texture": "#radar_face_b"
},
"west": {
"uv": [
0,
0,
4.0,
8.0
],
"texture": "#radar_face_b"
},
"up": {
"uv": [
0,
0,
10.0,
4.0
],
"texture": "#radar_face_b"
},
"down": {
"uv": [
0,
0,
10.0,
4.0
],
"texture": "#radar_face_b"
}
}
},
{
"from": [
7.0,
13.0,
10.0
],
"to": [
9.0,
18.0,
12.0
],
"faces": {
"north": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal_b"
},
"south": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal_b"
},
"east": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal_b"
},
"west": {
"uv": [
0,
0,
2.0,
5.0
],
"texture": "#metal_b"
},
"up": {
"uv": [
0,
0,
2.0,
2.0
],
"texture": "#metal_b"
},
"down": {
"uv": [
0,
0,
2.0,
2.0
],
"texture": "#metal_b"
}
}
},
{
"from": [
-4.7,
4.8,
-4.0
],
"to": [
-2.5,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
2.2,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
2.2,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
2.2,
16
],
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
2.2,
16
],
"texture": "#flecktarn_b"
}
},
"rotation": {
"angle": 22.5,
"axis": "z",
"origin": [
-3.6,
7.75,
5.0
]
}
},
{
"from": [
18.5,
4.8,
-4.0
],
"to": [
20.7,
10.7,
14.0
],
"faces": {
"north": {
"uv": [
0,
0,
2.1999999999999993,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"south": {
"uv": [
0,
0,
2.1999999999999993,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"east": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"west": {
"uv": [
0,
0,
16,
5.8999999999999995
],
"texture": "#flecktarn_b"
},
"up": {
"uv": [
0,
0,
2.1999999999999993,
16
],
"texture": "#flecktarn_b"
},
"down": {
"uv": [
0,
0,
2.1999999999999993,
16
],
"texture": "#flecktarn_b"
}
},
"rotation": {
"angle": -22.5,
"axis": "z",
"origin": [
19.6,
7.75,
5.0
]
}
},
{
"from": [
0.0,
13.05,
2.0
],
"to": [
5.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"south": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"east": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"west": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"up": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal_b"
},
"down": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal_b"
}
}
},
{
"from": [
10.0,
13.05,
2.0
],
"to": [
15.0,
13.7,
8.0
],
"faces": {
"north": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"south": {
"uv": [
0,
0,
5.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"east": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"west": {
"uv": [
0,
0,
6.0,
0.6499999999999986
],
"texture": "#dark_metal_b"
},
"up": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal_b"
},
"down": {
"uv": [
0,
0,
5.0,
6.0
],
"texture": "#dark_metal_b"
}
}
},
{
"from": [
-2.6199999999999974,
6.0,
-4.0
],
"to": [
-2.5,
9.0,
2.0
],
"faces": {
"north": {
"uv": [
0,
0,
0.25,
3.0
],
"texture": "#mk_de_b"
},
"south": {
"uv": [
0,
0,
0.25,
3.0
],
"texture": "#mk_de_b"
},
"east": {
"uv": [
0,
0,
6.0,
3.0
],
"texture": "#mk_de_b"
},
"west": {
"uv": [
0,
0,
6.0,
3.0
],
"texture": "#mk_de_b"
},
"up": {
"uv": [
0,
0,
0.25,
6.0
],
"texture": "#mk_de_b"
},
"down": {
"uv": [
0,
0,
0.25,
6.0
],
"texture": "#mk_de_b"
}
}
}
]
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+99
View File
@@ -0,0 +1,99 @@
// Generated by tools/gen_models.py from the final staged models; do not edit.
pub fn bounds(id: &str) -> Option<([f64; 3], [f64; 3])> {
Some(match id {
"airdef:amraam_missile" => ([0.97, 0.97, -16.0], [15.03, 15.03, 25.9]),
"airdef:barge" => ([-16.0, 0.0, -16.0], [32.0, 6.5, 32.0]),
"airdef:beam" => ([6.5, 6.5, -16.0], [9.5, 9.5, 32.0]),
"airdef:buk_chassis" => ([-14.5, 0.0, -16.0], [30.5, 17.4, 32.0]),
"airdef:buk_chassis_burnt" => ([-14.5, 0.0, -16.0], [30.5, 17.4, 32.0]),
"airdef:buk_rails" => ([-12.0, 0.0, -16.0], [28.0, 7.5, 27.0]),
"airdef:buk_rails_burnt" => ([-12.0, 0.0, -16.0], [28.0, 7.5, 27.0]),
"airdef:buk_turret" => ([-4.0, 0.0, -10.0], [20.0, 14.0, 20.0]),
"airdef:buk_turret_burnt" => ([-4.0, 0.0, -10.0], [20.0, 14.0, 20.0]),
"airdef:debris" => ([2.0, 6.0, 3.0], [14.0, 9.0, 13.0]),
"airdef:depot" => ([-14.0, 0.0, -14.0], [30.0, 17.0, 30.0]),
"airdef:depot_burnt" => ([-14.0, 0.0, -14.0], [30.0, 17.0, 30.0]),
"airdef:ew_truck" => ([-9.0, -1.379664, -16.0], [25.0, 32.0, 33.379664]),
"airdef:ew_truck_burnt" => ([-9.0, -1.379664, -16.0], [25.0, 22.0, 33.379664]),
"airdef:fireball" => ([0.0, 0.0, 0.0], [16.0, 16.0, 16.0]),
"airdef:flame" => ([5.625, 5.625, -8.0], [10.375, 10.375, 26.0]),
"airdef:flare" => ([6.5, 6.5, 6.5], [9.5, 16.0, 9.5]),
"airdef:flash" => ([-4.0, -4.0, -4.0], [20.0, 20.0, 20.0]),
"airdef:gepard_dish" => ([-4.0, 0.0, 6.0], [20.0, 14.0, 8.0]),
"airdef:gepard_guns" => ([-9.0, 3.5, -16.0], [25.0, 9.0, 14.0]),
"airdef:gepard_guns_burnt" => ([-9.0, 3.5, -16.0], [25.0, 9.0, 14.0]),
"airdef:gepard_hull" => ([-14.5, 0.0, -15.0], [30.5, 17.2, 31.0]),
"airdef:gepard_hull_burnt" => ([-14.5, 0.0, -15.0], [30.5, 17.2, 31.0]),
"airdef:gepard_turret" => ([-5.745184, 0.0, -10.0], [21.745184, 18.0, 16.0]),
"airdef:gepard_turret_burnt" => ([-5.745184, 0.0, -10.0], [21.745184, 18.0, 16.0]),
"airdef:geran3" => ([-10.0, 4.341543, -16.0], [26.0, 17.005161, 29.9]),
"airdef:geran3_flame" => ([6.15, 6.15, 6.0], [9.85, 9.85, 24.0]),
"airdef:gerbera" => ([-9.0, 3.0, -15.0], [25.0, 13.0, 24.5]),
"airdef:irist_chassis" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]),
"airdef:irist_chassis_burnt" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]),
"airdef:irist_missile" => ([1.47, 1.47, -16.0], [14.53, 14.53, 23.9]),
"airdef:irist_tubes" => ([-2.5, 0.0, -16.0], [18.5, 9.0, 32.0]),
"airdef:irist_tubes_burnt" => ([-2.5, 0.0, -16.0], [18.5, 9.0, 32.0]),
"airdef:iskander" => ([-1.74, -1.74, -16.0], [17.74, 17.74, 29.9]),
"airdef:kalibr" => ([-6.0, 1.52, -16.0], [22.0, 14.48, 29.9]),
"airdef:kh101" => ([-12.5, 3.2, -16.0], [28.5, 16.0, 28.4]),
"airdef:kh22" => ([-10.0, -1.2, -16.0], [26.0, 17.2, 29.9]),
"airdef:kinzhal" => ([-3.06, -3.06, -16.0], [19.06, 19.06, 29.9]),
"airdef:lancet" => ([-1.40452, -1.40452, -16.0], [17.40452, 17.40452, 24.5]),
"airdef:launch_ramp" => ([1.0, 0.0, -12.708033], [15.0, 19.342915, 32.0]),
"airdef:launcher_truck" => ([-10.5, -1.379664, -16.379664], [26.5, 31.288582, 32.0]),
"airdef:m48n6" => ([0.09, 0.09, -16.0], [15.91, 15.91, 27.9]),
"airdef:m57e6" => ([1.47, 1.47, -16.0], [14.53, 14.53, 26.9]),
"airdef:m77n6" => ([0.82, 0.82, -16.0], [15.18, 15.18, 29.9]),
"airdef:nasams_launcher" => ([0.0, 0.0, -16.0], [16.0, 9.0, 32.0]),
"airdef:nasams_launcher_burnt" => ([0.0, 0.0, -16.0], [16.0, 9.0, 32.0]),
"airdef:nasams_trailer" => ([-9.0, -1.379664, -16.0], [25.0, 13.0, 26.0]),
"airdef:nasams_trailer_burnt" => ([-9.0, -1.379664, -16.0], [25.0, 13.0, 26.0]),
"airdef:p1sun" => ([-0.5, -0.5, -14.0], [16.5, 16.5, 10.0]),
"airdef:p1sun_launcher" => ([-0.5, 0.0, -16.0], [16.5, 7.0, 32.0]),
"airdef:p1sun_launcher_burnt" => ([-0.5, 0.0, -16.0], [16.5, 7.0, 32.0]),
"airdef:pac3" => ([1.47, 1.47, -16.0], [14.53, 14.53, 25.9]),
"airdef:pantsir_dish" => ([-6.5, -8.0, 7.0], [22.5, 16.5, 10.0]),
"airdef:pantsir_pack" => ([-11.0, 2.0, -16.0], [27.0, 15.0, 12.0]),
"airdef:pantsir_pack_burnt" => ([-11.0, 2.0, -16.0], [27.0, 15.0, 12.0]),
"airdef:pantsir_truck" => ([-10.5, -1.379664, -16.0], [26.5, 21.0, 33.379664]),
"airdef:pantsir_truck_burnt" => ([-10.5, -1.379664, -16.0], [26.5, 21.0, 33.379664]),
"airdef:pantsir_turret" => ([-2.12, 0.0, -11.0], [18.12, 17.0, 16.0]),
"airdef:pantsir_turret_burnt" => ([-2.12, 0.0, -11.0], [18.0, 17.0, 16.0]),
"airdef:patriot_launcher" => ([-8.0, 0.0, -14.0], [24.0, 19.0, 30.0]),
"airdef:patriot_launcher_burnt" => ([-8.0, 0.0, -14.0], [24.0, 19.0, 30.0]),
"airdef:patriot_radar" => ([-9.0, -1.410323, -10.0], [25.0, 30.0, 26.0]),
"airdef:patriot_trailer" => ([-10.0, -1.410323, -16.0], [26.0, 16.0, 33.010323]),
"airdef:patriot_trailer_burnt" => ([-10.0, -1.410323, -16.0], [26.0, 16.0, 33.010323]),
"airdef:patriot_truck" => ([-9.0, -1.410323, -16.0], [25.0, 25.0, 33.010323]),
"airdef:radar_panel" => ([-10.5, -6.0, 7.0], [26.5, 26.5, 12.0]),
"airdef:radar_trailer" => ([-9.0, -1.379664, -12.0], [25.0, 18.0, 26.379664]),
"airdef:radar_truck" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]),
"airdef:radar_truck_burnt" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]),
"airdef:reentry" => ([6.5, 6.5, -16.0], [9.5, 9.5, 32.0]),
"airdef:s400_chassis" => ([-10.5, 0.0, -16.460181], [26.5, 22.0, 32.0]),
"airdef:s400_chassis_burnt" => ([-10.5, 0.0, -16.460181], [26.5, 19.5, 32.0]),
"airdef:s400_tubes" => ([-0.5, 0.0, -16.0], [16.5, 15.0, 32.0]),
"airdef:s400_tubes_burnt" => ([-0.5, 0.0, -16.0], [16.5, 15.0, 32.0]),
"airdef:s500_chassis" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]),
"airdef:s500_chassis_burnt" => ([-10.5, -1.379664, -16.379664], [26.5, 22.0, 32.0]),
"airdef:s500_tubes" => ([-3.0, 0.0, -16.0], [19.0, 9.5, 32.0]),
"airdef:s500_tubes_burnt" => ([-3.0, 0.0, -16.0], [19.0, 9.5, 32.0]),
"airdef:searchlight" => ([2.0, 0.0, 2.0], [14.0, 12.0, 14.0]),
"airdef:searchlight_burnt" => ([2.0, 0.0, 2.0], [14.0, 12.0, 14.0]),
"airdef:searchlight_head" => ([3.0, 3.0, -2.5], [13.0, 13.0, 14.0]),
"airdef:searchlight_head_burnt" => ([3.0, 3.0, -2.5], [13.0, 13.0, 14.0]),
"airdef:shahed" => ([-13.0, 4.73352, -16.0], [29.0, 13.2, 28.0]),
"airdef:shahed_prop" => ([-4.0, -4.0, 7.0], [20.0, 20.0, 9.5]),
"airdef:target" => ([2.0, -8.0, 2.0], [14.0, 24.0, 14.0]),
"airdef:tor_chassis" => ([-14.5, 0.0, -16.0], [30.5, 17.4, 32.0]),
"airdef:tor_chassis_burnt" => ([-14.5, 0.0, -16.0], [30.5, 17.4, 32.0]),
"airdef:tor_turret" => ([-4.0, 0.0, -11.0], [20.0, 17.5, 22.0]),
"airdef:tor_turret_burnt" => ([-4.0, 0.0, -11.0], [20.0, 17.5, 22.0]),
"airdef:tracer" => ([7.3, 7.3, -16.0], [8.7, 8.7, 32.0]),
"airdef:tzm_truck" => ([-9.0, -1.379664, -16.0], [25.0, 26.0, 33.379664]),
"airdef:tzm_truck_burnt" => ([-9.0, -1.379664, -16.0], [25.0, 26.0, 33.379664]),
"airdef:zircon" => ([0.16, 0.16, -16.0], [15.84, 15.84, 29.9]),
_ => return None,
})
}
+1
View File
@@ -10,6 +10,7 @@ use glam::{DQuat, DVec3};
use std::collections::HashMap;
pub mod catalog;
mod generated_bounds;
mod damage;
pub mod doctrine;
pub mod fires;
+33 -3
View File
@@ -81,7 +81,7 @@ struct Chassis {
impl Chassis {
fn for_kind(kind: SamKind) -> Self {
let (radius, height) = match kind.spec().family {
let (radius, height): (f64, f64) = match kind.spec().family {
Family::Trainable => (2.25, 3.0),
Family::Gun => (1.75, 3.0),
Family::Turret => (2.0, 3.25),
@@ -90,14 +90,27 @@ impl Chassis {
Family::Truck => (1.75, 2.75),
Family::Light => (1.25, 2.5),
};
// Rendered geometry may extend past the nominal family envelope (rotated wheels too).
// Read the generated bounds of the exact body rather than letting art silently shrink clearance.
let (radius, height) = match super::generated_bounds::bounds(kind.spec().parts.body) {
Some((lo, hi)) => {
let scale = kind.spec().scale / 16.0;
let half_x = (lo[0] - 8.0).abs().max((hi[0] - 8.0).abs()) * scale;
let half_z = (lo[2] - 8.0).abs().max((hi[2] - 8.0).abs()) * scale;
(radius.max(half_x.hypot(half_z) + 0.125), height.max(hi[1] * scale))
}
None => (radius, height), // Content validation rejects missing models before packaging.
};
Self { radius, height }
}
/// Integer voxel sample points inside the conservative footprint.
/// Every voxel whose square intersects the conservative footprint, including grazing corners.
fn samples(self) -> Vec<(i32, i32)> {
let r = self.radius.ceil() as i32;
(-r..=r).flat_map(|x| (-r..=r).filter_map(move |z| {
((x * x + z * z) as f64 <= self.radius * self.radius + 0.5).then_some((x, z))
let dx = (f64::from(x.abs()) - 0.5).max(0.0);
let dz = (f64::from(z.abs()) - 0.5).max(0.0);
(dx * dx + dz * dz <= self.radius * self.radius).then_some((x, z))
})).collect()
}
}
@@ -569,6 +582,23 @@ impl SamKind {
mod tests {
use super::*;
#[test]
fn gun_chassis_cannot_use_the_old_one_point_seven_five_block_radius() {
// The rendered Gepard hull alone is wider/longer than that nominal family circle.
assert!(Chassis::for_kind(SamKind::Gepard).radius > 2.5);
for kind in SamKind::ALL {
assert!(super::super::generated_bounds::bounds(kind.spec().parts.body).is_some(), "{kind:?}");
}
}
#[test]
fn footprint_checks_voxels_grazed_between_their_centres() {
let cells = Chassis { radius: 2.75, height: 3.0 }.samples();
assert!(cells.contains(&(3, 1)), "the body reaches the near corner of this voxel");
assert!(cells.contains(&(-3, -1)));
assert!(!cells.contains(&(3, 3)));
}
/// The land as the game would tell it: the height map, and under it the blocks that are solid.
struct Src<H: Fn(i32, i32) -> i32, B: Fn(i32, i32) -> i32> {
height: H,
+67
View File
@@ -0,0 +1,67 @@
# Пилотный художественный проход (#115)
Это промежуточные модели для оценки направления, а не завершённая переработка всего набора.
Изменены С-400 (шасси и ТПК), Гепард (корпус и башня), Шахед. Исходный шумный камуфляж
в основном сохранён: следующий художественный этап — спокойнее материалы, яснее крупные формы
и плотность пикселей. Число элементов само по себе не является доказательством качества.
| Пилот | До | После |
|---|---|---|
| С-400 | ![С-400 до](media/model-pilots/before-s400-day.png) | ![С-400 после](media/model-pilots/after-s400-day.png) |
| Гепард | ![Гепард до](media/model-pilots/before-gepard-day.png) | ![Гепард после](media/model-pilots/after-gepard-day.png) |
| Шахед | ![Шахед до](media/model-pilots/before-shahed-day.png) | ![Шахед после](media/model-pilots/after-shahed-day.png) |
## Что изменено
- С-400: округлые ступенчатые колёса без звёздчатого пересечения двух квадратов, ступицы,
разделённые кабины со стеклом/крышей, читаемая рама; четыре ТПК с округлым сечением и обвязкой.
- Гепард: наклонная лобовая часть, отдельная верхняя плита, катки на непрерывной гусенице,
скошенные стороны башни и люки. Подвижные пушки и обзорная антенна используют прежние крепления.
- Шахед: более плавный контур носа/крыла, тоньше крыло, переработаны законцовки и задняя часть;
пропеллер остаётся отдельной анимируемой частью. Это стилизация существующего игрового прототипа.
- Наземные пилоты имеют соответствующие обгоревшие варианты.
Геометрия сделана внутри проекта; исходные игровые текстуры не заимствованы из внешних фотографий.
Визуальные референсы: [Гепард, Bundeswehr](https://www.bundeswehr.de/de/meldungen/flugabwehrkanonenpanzer-gepard-5400072),
[51П6, фотография автора Mike1979 Russia](https://commons.wikimedia.org/wiki/File:51P6_launcher_S-400_Triumf_Army-2022_2022-08-20_2416.jpg).
Это ориентиры внешнего вида, не утверждение о точном воспроизведении конкретной модификации.
## Метод сравнения и ограничения
Настоящий Horizon/AirDef, macOS arm64, Apple M5/Metal, offscreen 2560×1440. Временный игровой home,
ровный полигон, ясная погода, day_time 6000, spectator. Для пары используются одинаковые WASM,
движок и сцена: в пакете «до» заменены только девять JSON пилотов на их версии из `4a3500f`.
Скриншоты не ретушировались. Камера проверена по status после переноса:
| Пилот | Позиция камеры | yaw / pitch |
|---|---|---|
| С-400 | -4, 114, 25 | -30 / 12 |
| Гепард | 20, 114, 25 | 30 / 18 |
| Шахед | 28, 115, 27 | 35 / 30 |
РЛС продолжает вращаться; её угол и фаза облаков в паре могут различаться. Пилот Шахеда показан как
статичная модель для сравнения геометрии. Здесь не проверены все ракурсы, полёт/пуск, повреждение
в кино и массовая нагрузка. Native HDR не включался; HD — отдельный режим рендера мода.
[Общий вид до, HD](media/model-pilots/before-overview-hd.png) ·
[Общий вид после, HD](media/model-pilots/after-overview-hd.png) ·
[После ночью, HD](media/model-pilots/after-overview-night-hd.png).
## Бюджет и редактирование
| Часть | Элементов до | После |
|---|---:|---:|
| s400_chassis | 35 | 78 |
| s400_tubes | 6 | 29 |
| gepard_hull | 8 | 55 |
| gepard_turret | 12 | 11 |
| shahed | 26 | 29 |
Новые runtime IDs или отдельные части сцены не добавлены. Рост числа граней требует замера на массовом
сценарии; отсутствие роста draw calls или сохранение FPS этим отчётом не обещается.
Редактируемые JSON: `airdef-mod/source-assets/assets/airdef/models/block/`.
Необязательный экспортёр: `python tools/pilot_models.py` (перезаписывает только эти пилоты).
Установка/валидация: `python tools/gen_models.py`. Габариты для навигации вычисляются из итоговой
геометрии после authored overrides, включая поворот элементов. Стандартная regeneration сохраняет
авторские исходники. Полный набор и художественная приёмка остаются в #115.
+5 -1
View File
@@ -44,7 +44,9 @@
## Авторские и сгенерированные ресурсы
- `tools/gen_models.py` генерирует геометрию моделей, блоковые состояния, item registry, локализацию и data JSON.
- `tools/gen_models.py` генерирует геометрию моделей и управляет сборкой набора.
- `tools/airdef_assets/registry.py` создаёт blockstate/item registry, локализацию и data JSON.
- `tools/airdef_assets/bounds.py` экспортирует габариты итоговых моделей для headless-навигации; authored overrides входят в расчёт.
- `tools/gen_tex.py` отвечает за вывод процедурных текстур и иконок; переиспользуемые примитивы палитры также находятся здесь.
- `tools/airdef_assets/texture_catalog.py` связывает стабильную палитру и процедурные функции с текстурами моделей, не смешивая каталог с геометрией.
- `tools/airdef_assets/icons.py` содержит каталог предметов и чистую функцию рендера иконки с явными параметрами `(name, color, seed)`.
@@ -53,3 +55,5 @@
- `airdef-mod/assets/` и `airdef-mod/data/` — упакованные generated output. Не редактируйте их вручную; добавляйте авторские исходники в `source-assets`.
- `airdef-mod/generated-manifest.txt` хранит SHA-256 только для управляемого результата. Неизменённые старые outputs можно заменить или удалить; посторонние и вручную правленные файлы блокируют конфликтующий запуск.
- `python3 tools/test_gen_assets.py` проверяет manifest, защиту ручных файлов, откат при ошибке записи, детерминизм и полный цикл authored icon override/validation.
`airdef-mod/src/sim/generated_bounds.rs` также управляется manifest: это данные геометрии, а не файл для ручного редактирования. Пилотный арт и сравнения — в `docs/MODEL-PILOTS.md`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+42
View File
@@ -0,0 +1,42 @@
"""Export conservative model bounds for SDK-free chassis navigation."""
import itertools
import json
import math
from pathlib import Path
def model_bounds(model):
points = []
for element in model.get("elements", []):
rotation = element.get("rotation")
for point in itertools.product(*zip(element["from"], element["to"])):
point = list(point)
if rotation:
axis = "xyz".index(rotation["axis"])
a, b = (axis + 1) % 3, (axis + 2) % 3
origin = rotation["origin"]
x, y = point[a] - origin[a], point[b] - origin[b]
angle = math.radians(rotation["angle"])
# Rescaled rotations expand in the plane perpendicular to the rotation axis.
scale = 1 / math.cos(angle) if rotation.get("rescale") else 1
point[a] = origin[a] + (x * math.cos(angle) - y * math.sin(angle)) * scale
point[b] = origin[b] + (x * math.sin(angle) + y * math.cos(angle)) * scale
points.append(point)
if not points:
return None
return tuple([min(p[a] for p in points) for a in range(3)]), tuple([max(p[a] for p in points) for a in range(3)])
def write_rust_bounds(stage):
stage = Path(stage)
rows = ["// Generated by tools/gen_models.py from the final staged models; do not edit.\n",
"pub fn bounds(id: &str) -> Option<([f64; 3], [f64; 3])> {\n Some(match id {\n"]
for path in sorted((stage / "assets/airdef/models/block").glob("*.json")):
bounds = model_bounds(json.loads(path.read_text(encoding="utf-8")))
if bounds:
lo, hi = ([round(float(x), 6) for x in corner] for corner in bounds)
rows.append(f' "airdef:{path.stem}" => ({lo}, {hi}),\n')
rows.append(" _ => return None,\n })\n}\n")
target = stage / "src/sim/generated_bounds.rs"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("".join(rows), encoding="utf-8")
+40
View File
@@ -0,0 +1,40 @@
"""Resource registrations and localization, independent of geometry and raster generation."""
import json
import os
def write_json(path, obj):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, ensure_ascii=False, indent=1)
def write_registry(out, namespace, models, items, camera_models):
a = os.path.join(out, "assets", namespace)
d = os.path.join(out, "data", namespace, "horizon")
lang = {}
NS = namespace
for name, title in models:
write_json(os.path.join(a, "blockstates", name + ".json"), {"variants": {"": {"model": f"{NS}:block/{name}"}}})
write_json(os.path.join(d, "block", name + ".json"),
{"template": "minecraft:stone", "item": False})
lang[f"block.{NS}.{name}"] = title
for name, title, _ in items:
write_json(os.path.join(a, "models", "item", name + ".json"),
{"parent": "minecraft:item/generated", "textures": {"layer0": f"{NS}:item/{name}"}})
write_json(os.path.join(a, "items", name + ".json"),
{"model": {"type": "minecraft:model", "model": f"{NS}:item/{name}"}})
write_json(os.path.join(d, "item", name + ".json"), {"template": "minecraft:stick", "max_stack_size": 1})
lang[f"item.{NS}.{name}"] = title
# Предметы-иконки для камеры монитора: 3D-модель цели в интерфейсе.
for name in camera_models:
title = dict(models)[name]
write_json(os.path.join(a, "models", "item", "cam_" + name + ".json"),
{"parent": f"{NS}:block/{name}",
"display": {"gui": {"rotation": [20, 145, 0], "translation": [0, 0, 0], "scale": [0.36, 0.36, 0.36]}}})
write_json(os.path.join(a, "items", "cam_" + name + ".json"),
{"model": {"type": "minecraft:model", "model": f"{NS}:item/cam_{name}"}})
write_json(os.path.join(d, "item", "cam_" + name + ".json"), {"template": "minecraft:stick", "max_stack_size": 1})
lang[f"item.{NS}.cam_{name}"] = title
write_json(os.path.join(a, "lang", "en_us.json"), lang)
write_json(os.path.join(a, "lang", "ru_ru.json"), lang)
+6 -30
View File
@@ -24,6 +24,8 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from PIL import Image
import gen_tex as GT
from airdef_assets import pipeline
from airdef_assets.bounds import write_rust_bounds
from airdef_assets.registry import write_json, write_registry
from airdef_assets.texture_catalog import RINGED, STRETCH_FN, TEXTURES
from airdef_assets.icons import ITEMS, render_icon
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "airdef-mod"))
@@ -1042,11 +1044,6 @@ WRECKS = ["patriot_trailer", "patriot_launcher", "gepard_hull", "gepard_turret",
CAM_MODELS = ["shahed", "geran3", "lancet", "gerbera", "kh101", "kalibr", "kh22", "zircon", "iskander", "kinzhal", "reentry"]
def write_json(path, obj):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, ensure_ascii=False, indent=1)
def generate(out):
# Пишет весь набор в каталог out (зеркало airdef-mod/). Звуки (tools/gen_sounds.py) и sounds.json не трогаем.
@@ -1054,37 +1051,15 @@ def generate(out):
d = os.path.join(out, "data", NS, "horizon")
os.makedirs(os.path.join(a, "textures", "block"))
os.makedirs(os.path.join(a, "textures", "item"))
lang = {}
for wid in WRECKS:
fn, title = MODELS[wid]
MODELS[wid + "_burnt"] = ((lambda fn=fn, wid=wid: burnt_parts(fn(), zlib.crc32(wid.encode()))), title + " (остов)")
for name, (fn, title) in MODELS.items():
for name, (fn, _) in MODELS.items():
write_json(os.path.join(a, "models", "block", name + ".json"), model(fn(), name.endswith("_burnt")))
write_json(os.path.join(a, "blockstates", name + ".json"), {"variants": {"": {"model": f"{NS}:block/{name}"}}})
write_json(os.path.join(d, "block", name + ".json"),
{"template": "minecraft:stone", "item": False})
lang[f"block.{NS}.{name}"] = title
GT.write_block_textures(a, TEXTURES, VARIANTS, STRETCH_FN)
for i, (name, title, color) in enumerate(ITEMS):
for name, _, color in ITEMS:
GT.write_item_icon(a, name, render_icon(name, color, zlib.crc32(name.encode())))
write_json(os.path.join(a, "models", "item", name + ".json"),
{"parent": "minecraft:item/generated", "textures": {"layer0": f"{NS}:item/{name}"}})
write_json(os.path.join(a, "items", name + ".json"),
{"model": {"type": "minecraft:model", "model": f"{NS}:item/{name}"}})
write_json(os.path.join(d, "item", name + ".json"), {"template": "minecraft:stick", "max_stack_size": 1})
lang[f"item.{NS}.{name}"] = title
# Предметы-иконки для камеры монитора: 3D-модель цели в интерфейсе.
for name in CAM_MODELS:
title = MODELS[name][1]
write_json(os.path.join(a, "models", "item", "cam_" + name + ".json"),
{"parent": f"{NS}:block/{name}",
"display": {"gui": {"rotation": [20, 145, 0], "translation": [0, 0, 0], "scale": [0.36, 0.36, 0.36]}}})
write_json(os.path.join(a, "items", "cam_" + name + ".json"),
{"model": {"type": "minecraft:model", "model": f"{NS}:item/cam_{name}"}})
write_json(os.path.join(d, "item", "cam_" + name + ".json"), {"template": "minecraft:stick", "max_stack_size": 1})
lang[f"item.{NS}.cam_{name}"] = title
write_json(os.path.join(a, "lang", "en_us.json"), lang)
write_json(os.path.join(a, "lang", "ru_ru.json"), lang)
write_registry(out, NS, [(name, title) for name, (_, title) in MODELS.items()], ITEMS, CAM_MODELS)
return len(MODELS), len(ITEMS), len(TEXTURES)
@@ -1210,6 +1185,7 @@ def regenerate(root=ROOT, source_root=None, manifest=None, *, check=False, repor
if errors:
details = "\n".join(f" - {err}" for err in errors)
raise ValueError(f"ПРОВЕРКА НАБОРА НЕ ПРОЙДЕНА, ошибок: {len(errors)}. Ничего не записано в {root}.\n{details}")
write_rust_bounds(stage)
new = {}
for dirpath, dirs, names in os.walk(stage):
dirs.sort()
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Export the three art pilots to authored JSON. Run gen_models.py afterwards.
The JSON under source-assets is the editable deliverable. This optional authoring helper
only rewrites its explicitly named pilots; the normal generator never calls it.
Existing origins, launcher grid and animation parts are retained.
"""
import json
from pathlib import Path
import zlib
import gen_models as g
ROOT = Path(g.ROOT)
SOURCE = ROOT / "source-assets/assets/airdef/models/block"
def tire(x0, x1, z, radius=4.5):
"""Five voxel strips: rounded silhouette, without overlapping rotated squares' star tips."""
parts = []
for lo, hi, width in [(-1, -.6, .6), (-.6, -.2, .92), (-.2, .2, 1), (.2, .6, .92), (.6, 1, .6)]:
parts += g.both(g.el((x0, radius + lo * radius, z - width * radius),
(x1, radius + hi * radius, z + width * radius), "tire"))
parts += g.both(g.el((x0 - .05, radius - 2.2, z - 2.2),
(x0, radius + 2.2, z + 2.2), "hub"))
return parts
def s400_chassis():
parts = [p for p in g.maz_chassis("ru_green") if p["tex"] != "tire"]
for z in [-10.5, -1.5, 16.5, 25.5]:
parts += tire(-9, -5.5, z)
# Cab roof and inset windscreen separate the cabin from the flat cargo deck.
for x0, x1 in [(-5.7, 2.7), (13.3, 21.7)]:
parts += [g.el((x0, 19, -15), (x1, 19.5, -7), "ru_green"),
g.el((x0 + .5, 15, -15.8), (x1 - .5, 18.5, -15.55), "glass_cab", rot=("x", 22.5))]
parts += g.both(g.B(-14.5, 8.3, -12.5, -14, 8.8, -7, "dark_metal"))
parts += g.both(g.B(-14.4, 13, -10.2, -14.1, 13.4, -8.4, "bare_antenna"))
# Underbody rails and an engine-deck grille read at normal gameplay distance.
parts += [g.el((1, 4.2, -8), (3, 5, 29), "dark_metal"),
g.el((13, 4.2, -8), (15, 5, 29), "dark_metal"),
g.el((-3, 13.1, -3), (19, 13.5, 3), "bare_nozzle")]
return parts
def s400_tubes():
parts = []
# Same four centres and 7-unit diameter as Muzzle::Grid; pivots are unchanged.
for x in [.5, 8.5]:
for y in [0, 8]:
for lo, hi, width in [(-1, -.6, .6), (-.6, -.2, .92), (-.2, .2, 1), (.2, .6, .92), (.6, 1, .6)]:
parts.append(g.el((x + 3.5 - 3.5 * width, y + 3.5 + lo * 3.5, -16),
(x + 3.5 + 3.5 * width, y + 3.5 + hi * 3.5, 32),
"tube", {"north": "tube_cap", "south": "tube_cap"}))
for z in [-1, 26]:
parts.append(g.el((x - .15, y + 2.6, z), (x + 7.15, y + 4.4, z + 1), "dark_metal"))
parts += [g.el((-.5, 0, 26), (16.5, 1, 29), "dark_metal")]
return parts
def gepard_hull():
parts = [g.el((-10, 4, -8), (26, 15, 30), "flecktarn"),
g.el((-10, 4, -15), (26, 9, -8), "flecktarn"),
g.el((-9.5, 9, -14), (25.5, 13, -7), "flecktarn", rot=("x", -22.5)),
g.el((-9, 15, -4), (25, 16.5, 18), "flecktarn"),
g.el((-8, 15, 18), (24, 16, 30), "dark_metal"),
g.el((-6, 16.6, -6), (3, 17.2, -1), "metal")]
# Side tracks remain a continuous loop; visible road wheels replace a featureless slab.
parts += g.both(g.el((-14, 0, -14), (-10, 8.8, 31), "track"))
for z in [-10, -4, 2, 8, 14, 20, 26]:
for lo, hi, width in [(-1, -.5, .65), (-.5, .5, 1), (.5, 1, .65)]:
parts += g.both(g.el((-14.08, 4.3 + lo * 3.5, z - width * 2.8),
(-14.02, 4.3 + hi * 3.5, z + width * 2.8), "hub"))
parts += g.both(g.el((-14.5, 8.6, -14), (-10, 10, 31), "flecktarn"))
for z in [20, 24, 28]:
parts += [g.el((-6, 16.1, z), (22, 16.3, z + 1), "bare_nozzle")]
return parts
def gepard_turret():
# Preserve the aiming origin, gun pack, tracking radar and search-dish attachment.
parts = [g.el((-4, 0, -5), (20, 5, 16), "flecktarn"),
g.el((-2.5, 5, -5), (18.5, 12, 15), "flecktarn"),
g.el((-2, 12, -2), (18, 13, 14), "flecktarn"),
g.el((3, 4, -10), (13, 12, -6), "radar_face"),
g.el((7, 13, 10), (9, 18, 12), "metal")]
parts += g.both(g.el((-4.7, 4.8, -4), (-2.5, 10.7, 14), "flecktarn", rot=("z", 22.5)))
parts += [g.el((0, 13.05, 2), (5, 13.7, 8), "dark_metal"),
g.el((10, 13.05, 2), (15, 13.7, 8), "dark_metal")]
parts += g.decal("mk_de", 6, 9, -4, 2, 10.5)
return parts
def shahed():
parts = g.ogive(-16, -9, 2.5, "shahed_nose", 4) + g.oct_z(-9, 22, 2.5, "shahed")
parts += g.delta2("shahed", "kh101_belly", -2, 21, 26, 21, 12, 7.5, 8.3)
parts += g.both(g.B(-20.8, 7.6, 18, -20.1, 13.2, 26, "shahed"))
parts += g.oct_z(22, 28, 1.8, "dark_metal")
parts += g.both(g.B(.8, 5.5, 24, 1.5, 6.6, 28, "bare_nozzle"))
parts += [g.B(-1.3, 10.5, -3, 1.3, 10.8, 7, "shahed")]
return parts
def export(name, parts, burnt=False):
model = g.model(parts, burnt=burnt)
# The authoring tool reuses existing textures; it never creates untracked palette variants.
for key, value in model["textures"].items():
if not (ROOT / "assets/airdef/textures" / (value.split(":", 1)[1] + ".png")).exists():
base = g.VARIANTS[key][0]
candidate = base + "_b" if burnt else base
if not (ROOT / "assets/airdef/textures/block" / (candidate + ".png")).exists():
candidate = base
model["textures"][key] = "airdef:block/" + candidate
SOURCE.mkdir(parents=True, exist_ok=True)
(SOURCE / (name + ".json")).write_text(json.dumps(model, indent=1) + "\n", encoding="utf-8")
if __name__ == "__main__":
for name, make in [("s400_chassis", s400_chassis), ("s400_tubes", s400_tubes),
("gepard_hull", gepard_hull), ("gepard_turret", gepard_turret), ("shahed", shahed)]:
parts = make()
export(name, parts)
if name != "shahed":
export(name + "_burnt", g.burnt_parts(parts, zlib.crc32(name.encode())), burnt=True)
print(name, len(parts), "elements")
+36
View File
@@ -0,0 +1,36 @@
import json
import math
from pathlib import Path
import tempfile
import unittest
from airdef_assets.bounds import model_bounds, write_rust_bounds
from airdef_assets.pipeline import overlay_sources
class BoundsTests(unittest.TestCase):
def test_rotated_wheel_bounds_include_the_corners_outside_its_original_box(self):
lo, hi = model_bounds({"elements": [{"from": [-1, -1, -1], "to": [1, 1, 1],
"rotation": {"axis": "x", "angle": 45, "origin": [0, 0, 0]}}]})
self.assertAlmostEqual(hi[1], math.sqrt(2))
self.assertAlmostEqual(lo[2], -math.sqrt(2))
self.assertEqual((lo[0], hi[0]), (-1, 1))
def test_final_authored_override_controls_exported_bounds(self):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
rel = Path("assets/airdef/models/block/test.json")
stage, source = root / "stage", root / "source"
for folder, extent in [(stage, 1), (source, 30)]:
(folder / rel).parent.mkdir(parents=True)
(folder / rel).write_text(json.dumps({"elements": [{"from": [0, 0, 0], "to": [extent, 2, 3]}]}))
overlay_sources(stage, source)
write_rust_bounds(stage)
text = (stage / "src/sim/generated_bounds.rs").read_text()
self.assertIn('"airdef:test"', text)
self.assertIn("[30.0, 2.0, 3.0]", text)
self.assertEqual(json.loads((source / rel).read_text())["elements"][0]["to"][0], 30)
if __name__ == "__main__":
unittest.main()