mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-31 23:09:58 +08:00
fixed # 修复作物绘制位置异常的BUG
This commit is contained in:
@@ -1,9 +1,166 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"mode": "full_canvas_bottom_center",
|
||||
"schema": 4,
|
||||
"mode": "visible_contact_center",
|
||||
"reference_soil_height": 79.5,
|
||||
"growth_scale": 0.75,
|
||||
"common_scale": 0.5,
|
||||
"wither_down": 8,
|
||||
"basis": "WebGLMainSceneLandController.SetCropTexture and mainscene RectTransform"
|
||||
"basis": "Local visible ground-contact centroid aligned to actual land center; upright uniform sprite scaling",
|
||||
"reference_soil_width": 153,
|
||||
"source_land_order": [
|
||||
1,
|
||||
4,
|
||||
7,
|
||||
10,
|
||||
13,
|
||||
16,
|
||||
2,
|
||||
5,
|
||||
8,
|
||||
11,
|
||||
14,
|
||||
17,
|
||||
3,
|
||||
6,
|
||||
9,
|
||||
12,
|
||||
15,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"source_land_centers": [
|
||||
[
|
||||
-55.50004959106445,
|
||||
162.5499725341797
|
||||
],
|
||||
[
|
||||
24.300010681152344,
|
||||
123.5999984741211
|
||||
],
|
||||
[
|
||||
104.4000015258789,
|
||||
84.69998168945312
|
||||
],
|
||||
[
|
||||
180.5999298095703,
|
||||
45.89997100830078
|
||||
],
|
||||
[
|
||||
256.9000244140625,
|
||||
7.49997615814209
|
||||
],
|
||||
[
|
||||
335.9000244140625,
|
||||
-30.500019073486328
|
||||
],
|
||||
[
|
||||
-132.0,
|
||||
122.80000305175781
|
||||
],
|
||||
[
|
||||
-52.199981689453125,
|
||||
83.8499984741211
|
||||
],
|
||||
[
|
||||
27.899959564208984,
|
||||
44.95000076293945
|
||||
],
|
||||
[
|
||||
104.09992218017578,
|
||||
6.14999532699585
|
||||
],
|
||||
[
|
||||
180.40000915527344,
|
||||
-32.25
|
||||
],
|
||||
[
|
||||
259.4000244140625,
|
||||
-70.25
|
||||
],
|
||||
[
|
||||
-208.50010681152344,
|
||||
83.05000305175781
|
||||
],
|
||||
[
|
||||
-128.6999969482422,
|
||||
44.099998474121094
|
||||
],
|
||||
[
|
||||
-48.60007095336914,
|
||||
5.200003623962402
|
||||
],
|
||||
[
|
||||
27.599895477294922,
|
||||
-33.600006103515625
|
||||
],
|
||||
[
|
||||
103.89999389648438,
|
||||
-72.0
|
||||
],
|
||||
[
|
||||
182.89999389648438,
|
||||
-110.0
|
||||
],
|
||||
[
|
||||
-285.0,
|
||||
43.29999923706055
|
||||
],
|
||||
[
|
||||
-205.199951171875,
|
||||
4.350001811981201
|
||||
],
|
||||
[
|
||||
-125.10001373291016,
|
||||
-34.549991607666016
|
||||
],
|
||||
[
|
||||
-48.90004348754883,
|
||||
-73.3499984741211
|
||||
],
|
||||
[
|
||||
27.400035858154297,
|
||||
-111.74999237060547
|
||||
],
|
||||
[
|
||||
106.40003967285156,
|
||||
-149.74998474121094
|
||||
],
|
||||
[
|
||||
-361.5000305175781,
|
||||
3.5499942302703857
|
||||
],
|
||||
[
|
||||
-281.6999206542969,
|
||||
-35.400001525878906
|
||||
],
|
||||
[
|
||||
-201.60000610351562,
|
||||
-74.30000305175781
|
||||
],
|
||||
[
|
||||
-125.40000915527344,
|
||||
-113.0999984741211
|
||||
],
|
||||
[
|
||||
-49.09993362426758,
|
||||
-151.49998474121094
|
||||
],
|
||||
[
|
||||
29.900068283081055,
|
||||
-189.49998474121094
|
||||
]
|
||||
],
|
||||
"contact_alpha_threshold": 32,
|
||||
"contact_band_fraction": 0.2,
|
||||
"sprite_axes": "upright"
|
||||
}
|
||||
|
||||
@@ -6479,13 +6479,185 @@
|
||||
"sample_rate": 24
|
||||
},
|
||||
"placement": {
|
||||
"basis": "WebGLMainSceneLandController.SetCropTexture and mainscene RectTransform",
|
||||
"basis": "Local visible ground-contact centroid aligned to actual land center; upright uniform sprite scaling",
|
||||
"common_scale": 0.5,
|
||||
"contact_alpha_threshold": 32,
|
||||
"contact_band_fraction": 0.2,
|
||||
"grid_fit": {
|
||||
"maximum": 9.082568081176591,
|
||||
"points": 30,
|
||||
"rms": 4.178633329197882
|
||||
},
|
||||
"growth_scale": 0.75,
|
||||
"mode": "full_canvas_bottom_center",
|
||||
"mode": "visible_contact_center",
|
||||
"reference_soil_height": 79.5,
|
||||
"schema": 2,
|
||||
"wither_down": 8
|
||||
"reference_soil_width": 153,
|
||||
"scene_transform": [
|
||||
[
|
||||
1.467423053281315,
|
||||
0.07509807118862355
|
||||
],
|
||||
[
|
||||
0.02736505575131576,
|
||||
-1.4715307877291894
|
||||
]
|
||||
],
|
||||
"schema": 4,
|
||||
"source_land_centers": [
|
||||
[
|
||||
-55.50004959106445,
|
||||
162.5499725341797
|
||||
],
|
||||
[
|
||||
24.300010681152344,
|
||||
123.5999984741211
|
||||
],
|
||||
[
|
||||
104.4000015258789,
|
||||
84.69998168945312
|
||||
],
|
||||
[
|
||||
180.5999298095703,
|
||||
45.89997100830078
|
||||
],
|
||||
[
|
||||
256.9000244140625,
|
||||
7.49997615814209
|
||||
],
|
||||
[
|
||||
335.9000244140625,
|
||||
-30.500019073486328
|
||||
],
|
||||
[
|
||||
-132.0,
|
||||
122.80000305175781
|
||||
],
|
||||
[
|
||||
-52.199981689453125,
|
||||
83.8499984741211
|
||||
],
|
||||
[
|
||||
27.899959564208984,
|
||||
44.95000076293945
|
||||
],
|
||||
[
|
||||
104.09992218017578,
|
||||
6.14999532699585
|
||||
],
|
||||
[
|
||||
180.40000915527344,
|
||||
-32.25
|
||||
],
|
||||
[
|
||||
259.4000244140625,
|
||||
-70.25
|
||||
],
|
||||
[
|
||||
-208.50010681152344,
|
||||
83.05000305175781
|
||||
],
|
||||
[
|
||||
-128.6999969482422,
|
||||
44.099998474121094
|
||||
],
|
||||
[
|
||||
-48.60007095336914,
|
||||
5.200003623962402
|
||||
],
|
||||
[
|
||||
27.599895477294922,
|
||||
-33.600006103515625
|
||||
],
|
||||
[
|
||||
103.89999389648438,
|
||||
-72.0
|
||||
],
|
||||
[
|
||||
182.89999389648438,
|
||||
-110.0
|
||||
],
|
||||
[
|
||||
-285.0,
|
||||
43.29999923706055
|
||||
],
|
||||
[
|
||||
-205.199951171875,
|
||||
4.350001811981201
|
||||
],
|
||||
[
|
||||
-125.10001373291016,
|
||||
-34.549991607666016
|
||||
],
|
||||
[
|
||||
-48.90004348754883,
|
||||
-73.3499984741211
|
||||
],
|
||||
[
|
||||
27.400035858154297,
|
||||
-111.74999237060547
|
||||
],
|
||||
[
|
||||
106.40003967285156,
|
||||
-149.74998474121094
|
||||
],
|
||||
[
|
||||
-361.5000305175781,
|
||||
3.5499942302703857
|
||||
],
|
||||
[
|
||||
-281.6999206542969,
|
||||
-35.400001525878906
|
||||
],
|
||||
[
|
||||
-201.60000610351562,
|
||||
-74.30000305175781
|
||||
],
|
||||
[
|
||||
-125.40000915527344,
|
||||
-113.0999984741211
|
||||
],
|
||||
[
|
||||
-49.09993362426758,
|
||||
-151.49998474121094
|
||||
],
|
||||
[
|
||||
29.900068283081055,
|
||||
-189.49998474121094
|
||||
]
|
||||
],
|
||||
"source_land_order": [
|
||||
1,
|
||||
4,
|
||||
7,
|
||||
10,
|
||||
13,
|
||||
16,
|
||||
2,
|
||||
5,
|
||||
8,
|
||||
11,
|
||||
14,
|
||||
17,
|
||||
3,
|
||||
6,
|
||||
9,
|
||||
12,
|
||||
15,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
],
|
||||
"sprite_axes": "upright"
|
||||
},
|
||||
"plants": {
|
||||
"ATTomato": {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""把 Unity 局部坐标映射到本地地块;保留完整纹理及透明留白。"""
|
||||
|
||||
from math import ceil, floor
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def ground_contact(image, centered=False, threshold=32, band_fraction=.2):
|
||||
alpha = image.convert('RGBA').getchannel('A')
|
||||
box = alpha.point(lambda value: 255 if value >= threshold else 0).getbbox()
|
||||
if box is None:
|
||||
return image.width/2, image.height/2
|
||||
left, top, right, bottom = box
|
||||
if centered:
|
||||
return (left+right)/2, (top+bottom)/2
|
||||
start = max(top, bottom-max(1, round((bottom-top)*band_fraction)))
|
||||
pixels = alpha.load()
|
||||
weight = x_sum = y_sum = 0
|
||||
for y in range(start, bottom):
|
||||
for x in range(left, right):
|
||||
value = pixels[x, y]
|
||||
if value >= threshold:
|
||||
weight += value
|
||||
x_sum += (x+.5)*value
|
||||
y_sum += (y+.5)*value
|
||||
return x_sum/weight, y_sum/weight
|
||||
|
||||
|
||||
def place_sprite(image, center, offset, pivot, factor, transform, scale):
|
||||
(a, b), (c, d) = transform
|
||||
anchor_x = (center[0]+a*offset[0]+b*offset[1])*scale
|
||||
anchor_y = (center[1]+c*offset[0]+d*offset[1])*scale
|
||||
m00, m01 = a*factor*scale, -b*factor*scale
|
||||
m10, m11 = c*factor*scale, -d*factor*scale
|
||||
px, py = image.width*pivot[0], image.height*pivot[1]
|
||||
corners = [(
|
||||
anchor_x+m00*(x-px)+m01*(y-py),
|
||||
anchor_y+m10*(x-px)+m11*(y-py),
|
||||
) for x, y in ((0, 0), (image.width, 0),
|
||||
(0, image.height), image.size)]
|
||||
left = floor(min(x for x, _ in corners))
|
||||
top = floor(min(y for _, y in corners))
|
||||
width = max(1, ceil(max(x for x, _ in corners))-left)
|
||||
height = max(1, ceil(max(y for _, y in corners))-top)
|
||||
det = m00*m11-m01*m10
|
||||
if abs(det) < 1e-12:
|
||||
raise ValueError('Degenerate scene transform')
|
||||
i00, i01, i10, i11 = m11/det, -m01/det, -m10/det, m00/det
|
||||
dx, dy = left-anchor_x, top-anchor_y
|
||||
result = image.transform(
|
||||
(width, height), Image.Transform.AFFINE,
|
||||
(i00, i01, px+i00*dx+i01*dy,
|
||||
i10, i11, py+i10*dx+i11*dy), Image.Resampling.BICUBIC,
|
||||
)
|
||||
return result, (left, top)
|
||||
+28
-31
@@ -4,6 +4,7 @@ from dataclasses import replace
|
||||
import hashlib
|
||||
from io import BytesIO
|
||||
import logging
|
||||
from math import sqrt
|
||||
from pathlib import Path
|
||||
from time import perf_counter
|
||||
|
||||
@@ -13,6 +14,7 @@ from .assets import Assets, ByteLRU
|
||||
from .canvas import Canvas, font
|
||||
from .layout import scene_from_snapshot
|
||||
from .models import RenderOptions, RenderResult
|
||||
from .placement import ground_contact, place_sprite
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
MAX_AVATAR_BYTES = 2 * 1024 * 1024
|
||||
@@ -138,7 +140,8 @@ class FarmRenderer:
|
||||
s = lambda n: self._scale(n, options)
|
||||
soil_w, soil_h = self.assets.metadata["soil_size"]
|
||||
rules = self.assets.metadata["placement"]
|
||||
unit = soil_h / rules["reference_soil_height"]
|
||||
(a, b), (c, d) = rules['scene_transform']
|
||||
scene_scale = sqrt(abs(a*d-b*c))
|
||||
first_ripe = True
|
||||
for plot in scene.plots:
|
||||
if not plot.sprite:
|
||||
@@ -147,29 +150,28 @@ class FarmRenderer:
|
||||
metadata = self.assets.metadata["images"].get(plot.sprite)
|
||||
if metadata is None:
|
||||
logger.warning("farm render missing packaged crop sprite")
|
||||
plant = Image.new("RGBA", (max(1, s(60)),) * 2, "#597b45")
|
||||
plant = Image.new("RGBA", (60, 60), "#597b45")
|
||||
ImageDraw.Draw(plant).text(
|
||||
(s(10), 0), "?", fill="white",
|
||||
font=font(self.font_path, max(10, s(48))),
|
||||
(10, 0), "?", fill="white",
|
||||
font=font(self.font_path, 48),
|
||||
)
|
||||
else:
|
||||
factor = rules["common_scale"] if (
|
||||
plot.stage == 0 or plot.state == "withered"
|
||||
) else rules["growth_scale"]
|
||||
size = [n * factor * unit for n in metadata["size"]]
|
||||
plant = self.assets.image(
|
||||
plot.sprite, tuple(max(1, s(n)) for n in size),
|
||||
)
|
||||
center_x = pos["x"] + soil_w / 2
|
||||
bottom = pos["y"] + soil_h
|
||||
plant = self.assets.image(plot.sprite)
|
||||
factor = rules['growth_scale']
|
||||
if plot.stage == 0 or plot.state == "withered":
|
||||
bottom = pos["y"] + soil_h / 2
|
||||
if plot.state == "withered":
|
||||
bottom += rules["wither_down"] * unit
|
||||
xy = (
|
||||
round(center_x * options.width / 3072 - plant.width / 2),
|
||||
s(bottom) - plant.height,
|
||||
factor = rules['common_scale']
|
||||
scale = scene_scale*factor*options.width/3072
|
||||
plant = plant.resize(
|
||||
tuple(max(1, round(n*scale)) for n in plant.size),
|
||||
Image.Resampling.LANCZOS,
|
||||
)
|
||||
contact = ground_contact(
|
||||
plant, centered=plot.stage == 0 and plot.state != 'withered',
|
||||
threshold=rules['contact_alpha_threshold'],
|
||||
band_fraction=rules['contact_band_fraction'],
|
||||
)
|
||||
xy = (round((pos['x']+soil_w/2)*options.width/3072-contact[0]),
|
||||
round((pos['y']+soil_h/2)*options.width/3072-contact[1]))
|
||||
canvas.paste(plant, xy)
|
||||
if plot.state == "ripe" and first_ripe:
|
||||
icon = self.assets.image(
|
||||
@@ -179,25 +181,20 @@ class FarmRenderer:
|
||||
first_ripe = False
|
||||
|
||||
def _pests(self, canvas, scene, options):
|
||||
s = lambda n: self._scale(n, options)
|
||||
soil_w, soil_h = self.assets.metadata["soil_size"]
|
||||
unit = soil_h / self.assets.metadata["placement"][
|
||||
"reference_soil_height"
|
||||
]
|
||||
transform = self.assets.metadata['placement']['scene_transform']
|
||||
config = self.assets.metadata["pest"]
|
||||
size = tuple(max(1, s(n * unit)) for n in config["display_size"])
|
||||
for plot in scene.plots:
|
||||
if plot.state not in ("growing", "ripe"):
|
||||
continue
|
||||
pos = self.assets.metadata["positions"][plot.slot - 1]
|
||||
for frame, (x, y) in zip(plot.pest_frames, config["positions"]):
|
||||
image = self.assets.image(frame, size)
|
||||
center_x = pos["x"] + soil_w / 2 + x * unit
|
||||
center_y = pos["y"] + soil_h / 2 - y * unit
|
||||
canvas.paste(image, (
|
||||
round(center_x * options.width / 3072 - image.width / 2),
|
||||
round(center_y * options.width / 3072 - image.height / 2),
|
||||
))
|
||||
image = self.assets.image(frame, config['display_size'])
|
||||
image, xy = place_sprite(
|
||||
image, (pos['x']+soil_w/2, pos['y']+soil_h/2),
|
||||
(x, y), (.5, .5), 1, transform, options.width/3072,
|
||||
)
|
||||
canvas.paste(image, xy)
|
||||
|
||||
def _avatar(self, content, size):
|
||||
if content and len(content) <= MAX_AVATAR_BYTES:
|
||||
|
||||
Reference in New Issue
Block a user