feat(jogo): expediente dos mortos, jogo de zumbis em primeira pessoa
Etapa 1 inteira (logica com 100% de cobertura, cena 3D, toque, sons, bancada de desempenho, e2e) e a base da etapa 2 (mapa novo no Tiled, texturas, imagens dos itens, sons conferidos, golpe recortado e medido). Pagina /jogo fora do menu e do sitemap ate a medicao no celular.
No files matched your search
@@ -0,0 +1,30 @@
|
||||
# Ferramentas do jogo
|
||||
|
||||
Programas baixados ficam em `3d/ferramentas/`, fora do git (`/3d/ferramentas/` no `.gitignore`), instalados sem sudo e sem login.
|
||||
|
||||
| programa | versão | licença (frase lida e arquivo) | como baixar | comando sem janela |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Tiled (com `tmxrasterizer`) | 1.12.2 | GPL-2.0 (editor) e BSD-2-Clause (`libtiled`, `tmxrasterizer`). Em `COPYING` do pacote: "Tiled src/tiled GPL … tmxrasterizer src/tmxrasterizer BSD 2-clause license … The full text of each license is provided in the files LICENSE.GPL and LICENSE.BSD." Arquivo: `/app/share/licenses/org.mapeditor.Tiled/tiled/COPYING` no Flatpak (o mesmo texto em `3d/ferramentas/tiled/squashfs-root/COPYING` no AppImage). | `flatpak install --user -y flathub org.mapeditor.Tiled`. O AppImage (`3d/ferramentas/tiled/`, das [releases](https://github.com/mapeditor/tiled/releases/tag/v1.12.2)) roda, mas só traz o plugin Qt `xcb` e falha com `QT_QPA_PLATFORM=offscreen`; `xvfb-run` não existe aqui. | `env -u DISPLAY -u WAYLAND_DISPLAY flatpak run --env=QT_QPA_PLATFORM=offscreen --command=tiled org.mapeditor.Tiled --export-map json "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.json"` e `env -u DISPLAY -u WAYLAND_DISPLAY flatpak run --env=QT_QPA_PLATFORM=offscreen --command=tmxrasterizer org.mapeditor.Tiled "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.png"` |
|
||||
| Blender | 5.2.2 LTS (`blender --version`) | GPL. Em `/usr/share/doc/blender/readme.html`: "Blender can be used freely for any purpose, including commercial use and distribution. It's free and open-source software, released under the GNU GPL licence." Bibliotecas de terceiros em `/usr/share/doc/blender/license/license.md`. | Já instalado pelo sistema (pacote `blender` 5.2.2-2 do Manjaro). | `blender -b -P script.py` |
|
||||
| MPFB (MakeHuman para Blender, extensão) | 2.0.18 (`blender_manifest.toml`; build 20261010) | Código GPL-3.0-or-later, recursos CC0. Em `LICENSE.md` do [repositório](https://github.com/makehumancommunity/mpfb2/blob/master/LICENSE.md): "The MPFB source (as defined per the above) is released under GPLv3." e "These assets have been released under CC0 1.0 Universal."; sobre a saída: "As the assets have been released under CC0, there is no limitation on what you can do with this combined output." O zip da extensão não traz arquivo de licença; o manifesto diz `license = ["SPDX:GPL-3.0-or-later"]`. | `3d/ferramentas/baixados/add-on-mpfb-v2.0.18.zip` de [extensions.blender.org](https://extensions.blender.org/add-ons/mpfb/) (sha256 `c075b3e9…2a2dad`), instalado só na pasta separada: `BLENDER_USER_RESOURCES=$PWD/3d/ferramentas/blender-user blender -b --factory-startup --command extension install-file -r user_default -e 3d/ferramentas/baixados/add-on-mpfb-v2.0.18.zip` | `BLENDER_USER_RESOURCES=$PWD/3d/ferramentas/blender-user blender -b -P 3d/humano.py` (instala os pacotes abaixo na pasta de dados do MPFB se faltarem) |
|
||||
|
||||
## Como mudar o mapa
|
||||
|
||||
1. Abrir `3d/mapa/escritorio.tmx` no Tiled: `flatpak run org.mapeditor.Tiled 3d/mapa/escritorio.tmx`.
|
||||
2. Editar as camadas `planta`, `moveis` e `marcas` e salvar.
|
||||
3. Rodar `bash 3d/mapa.sh`: exporta `3d/mapa/escritorio.json` e `escritorio.png`, escreve `lib/jogo/mapas/escritorio.ts` e monta `public/jogo/mapa.glb` no Blender.
|
||||
4. Rodar `pnpm test -- lib/jogo/mapas`.
|
||||
|
||||
O `.tmx` é a fonte. `bash 3d/mapa.sh --gerar` refaz o `.tmx` pelo `3d/mapa.py` e apaga o que foi editado à mão.
|
||||
|
||||
## Pacotes de recursos do MPFB
|
||||
|
||||
Baixados de `https://files.makehumancommunity.org/asset_packs/<pacote>/<pacote>_cc0.zip` (o link `files2` da [página](https://static.makehumancommunity.org/assets/assetpacks/index.html) dá o mesmo arquivo, mas a ~10 KB/s) para `3d/ferramentas/baixados/` e extraídos em `3d/ferramentas/blender-user/extensions/.user/user_default/mpfb/data/`. Nenhum zip traz arquivo de licença: a licença lida é o campo `"license"` de cada recurso em `packs/<pacote>.json` (dentro do zip) e o cabeçalho dos arquivos usados. Há cabeçalhos AGPL em alguns arquivos de pacotes marcados CC0 (ex.: `cortu_cargo_pants/cargo_pants.obj`, `blindsaypatten_uniform_skin_texture.mhmat`); esses ficaram de fora.
|
||||
|
||||
| pacote | sha256 | recursos usados em `3d/humano.py` | licença lida |
|
||||
| --- | --- | --- | --- |
|
||||
| makehuman_system_assets | `b542127a…0a0107` | pele `young_caucasian_male`, olhos `low-poly`, sobrancelha `eyebrow001`, cabelo `short02` (a malha base e o esqueleto `mixamo` vêm dentro da extensão, recursos CC0 pelo `LICENSE.md` acima) | CC0 nos 94 itens de `packs/makehuman_system_assets.json`; cabeçalho dos `.mhmat`/`.mhclo`: "This asset was explicitly released as CC0 in september 2020." |
|
||||
| shirts01 | `a5a723b0…3bcd3` | `namuhekam_male_polo_shirt` (autor namuhekam) | `"license": "CC0"` em `packs/shirts01.json`; CC0 no cabeçalho do `.mhclo`, `.obj` e `.mhmat` |
|
||||
| pants01 | `e4e0ec60…ad9` | `toigo_wool_pants` (autora MargaretToigo) | `"license": "CC0"` em `packs/pants01.json`; CC0 no cabeçalho do `.mhclo` e `.obj` |
|
||||
| shoes01 | `ded3f704…5dba9` | `toigo_ankle_boots_male` (autora MargaretToigo) | `"license": "CC0"` em `packs/shoes01.json`; CC0 no cabeçalho do `.mhclo` e `.obj` |
|
||||
| skins01, hair01 | `7495ab99…4ad9`, `49445d69…40c9` | baixados, não usados | CC0 no `packs/*.json`, mas com cabeçalhos AGPL em 2 e 10 arquivos |
|
||||
@@ -0,0 +1,333 @@
|
||||
"""Prova do MPFB2 (MakeHuman para Blender) por linha de comando: homem adulto magro, vestido, com esqueleto "mixamo".
|
||||
|
||||
Mede triângulos e ossos, passa os 6 clipes do zumbi atual (3d/fontes/zumbi, ossos mixamorig:*) para o esqueleto
|
||||
novo, exporta 3d/out/jogo/humano.glb e fotografa com a luz de zumbi-frente.png e zumbi-perto.png (3d/jogo.py).
|
||||
|
||||
Rodar SEMPRE com a pasta de usuário separada (a extensão e os pacotes ficam lá, fora do Blender do Evandro):
|
||||
BLENDER_USER_RESOURCES=$PWD/3d/ferramentas/blender-user blender -b -P 3d/humano.py
|
||||
A extensão entra uma vez com (ver 3d/FERRAMENTAS.md):
|
||||
BLENDER_USER_RESOURCES=… blender -b --factory-startup --command extension install-file -r user_default -e <zip>
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from blocos import RAIZ, SAIDA, falhar, ligar_cycles, sair # noqa: E402
|
||||
|
||||
try:
|
||||
from bl_ext.user_default.mpfb.services import AssetService, HumanService, LocationService
|
||||
except ImportError:
|
||||
falhar("MPFB não carregou: rode com BLENDER_USER_RESOURCES=3d/ferramentas/blender-user e instale a extensão")
|
||||
|
||||
BAIXADOS = os.path.join(RAIZ, "3d", "ferramentas", "baixados")
|
||||
CLIPES_FBX = os.path.join(RAIZ, "3d", "fontes", "zumbi") # baixados e extraídos por 3d/jogo.py
|
||||
RENDERS = os.path.join(SAIDA, "jogo")
|
||||
GLB = os.path.join(RENDERS, "humano.glb")
|
||||
|
||||
# pacote -> sha256 do zip (só pacotes CC0; https://static.makehumancommunity.org/assets/assetpacks/index.html)
|
||||
PACOTES = {
|
||||
"makehuman_system_assets": "b542127a8e25547c7c29c19f2d1d2adb9a664c80396ecd694095dbc8028a0107",
|
||||
"shirts01": "a5a723b0e84a109bb190fcfeac7f1de4138d875da3e30fe5b3340eac9f38bcd3",
|
||||
"pants01": "e4e0ec60db34f279be291a83cfd7b342a7c5cf09bb7676682a5f39f4f6ac4ad9",
|
||||
"shoes01": "ded3f70428505eabbf1f6d7b5f61196a7366ef20757103d276ad0ed336c35ada",
|
||||
}
|
||||
# files2 (o link da página) entrega a ~10 KB/s; files é o mesmo conteúdo (mesmo tamanho e sha256)
|
||||
URL_PACOTE = "https://files.makehumancommunity.org/asset_packs/{0}/{0}_cc0.zip"
|
||||
|
||||
# homem adulto (25 anos), magro, altura média; a raça fica no padrão do MPFB (1/3 de cada)
|
||||
CORPO = {"gender": 1.0, "age": 0.5, "muscle": 0.5, "weight": 0.3, "proportions": 0.5, "height": 0.5,
|
||||
"cupsize": 0.5, "firmness": 0.5, "race": {"asian": 0.33, "caucasian": 0.33, "african": 0.33}}
|
||||
PELE = "skins/young_caucasian_male/young_caucasian_male.mhmat"
|
||||
# (tipo do MPFB, arquivo na pasta de dados); escolhidos com cabeçalho CC0 nos .mhclo e .obj (cargo_pants.obj é AGPL)
|
||||
PECAS = [
|
||||
("Eyes", "eyes/low-poly/low-poly.mhclo"),
|
||||
("Eyebrows", "eyebrows/eyebrow001/eyebrow001.mhclo"),
|
||||
("Hair", "hair/short02/short02.mhclo"),
|
||||
("Clothes", "clothes/namuhekam_male_polo_shirt/namuhekam_male_polo_shirt.mhclo"),
|
||||
("Clothes", "clothes/toigo_wool_pants/toigo_wool_pants.mhclo"),
|
||||
("Clothes", "clothes/toigo_ankle_boots_male/toigo_ankle_boots_male.mhclo"), # o mj_cloth_shoes é sapatilha
|
||||
]
|
||||
ESQUELETO = "mixamo"
|
||||
MAX_TRIS = 12_000
|
||||
QUADRIL = "mixamorig:Hips"
|
||||
PERNA = ["UpLeg", "Leg", "Foot"]
|
||||
CLIPES = {
|
||||
"parado": "Zombie Idle.fbx",
|
||||
"andar": "Zombie Walk.fbx",
|
||||
"atacar": "Zombie Attack.fbx",
|
||||
"morrer": "Zombie Dying.fbx",
|
||||
"correr": "Zombie Running.fbx",
|
||||
"reacao": "Zombie Reaction Hit.fbx",
|
||||
}
|
||||
|
||||
|
||||
def sha256(caminho):
|
||||
h = hashlib.sha256()
|
||||
with open(caminho, "rb") as f:
|
||||
for bloco in iter(lambda: f.read(1 << 20), b""):
|
||||
h.update(bloco)
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def instalar_pacotes():
|
||||
dados = LocationService.get_user_data()
|
||||
for nome, soma in PACOTES.items():
|
||||
if os.path.exists(os.path.join(dados, "packs", nome + ".json")):
|
||||
continue
|
||||
zip_ = os.path.join(BAIXADOS, nome + "_cc0.zip")
|
||||
if not os.path.exists(zip_):
|
||||
os.makedirs(BAIXADOS, exist_ok=True)
|
||||
urllib.request.urlretrieve(URL_PACOTE.format(nome), zip_)
|
||||
if sha256(zip_) != soma:
|
||||
falhar(f"{zip_}: sha256 não confere")
|
||||
erro = AssetService.fix_and_extract_asset_pack_zip(zip_, dados)
|
||||
if erro:
|
||||
falhar(f"{nome}: {erro}")
|
||||
print(f"pacote {nome} instalado em {dados}", flush=True)
|
||||
AssetService.update_all_asset_lists()
|
||||
return dados
|
||||
|
||||
|
||||
def triangulos(obj):
|
||||
malha = obj.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
malha.calc_loop_triangles()
|
||||
n = len(malha.loop_triangles)
|
||||
obj.to_mesh_clear()
|
||||
return n
|
||||
|
||||
|
||||
def criar(dados):
|
||||
for obj in list(bpy.data.objects):
|
||||
bpy.data.objects.remove(obj)
|
||||
corpo = HumanService.create_human(macro_detail_dict=CORPO)
|
||||
esqueleto = HumanService.add_builtin_rig(corpo, ESQUELETO)
|
||||
HumanService.set_character_skin(os.path.join(dados, PELE), corpo, skin_type="MAKESKIN", material_instances=False)
|
||||
pecas = []
|
||||
for tipo, arquivo in PECAS:
|
||||
caminho = os.path.join(dados, arquivo)
|
||||
if not os.path.exists(caminho):
|
||||
falhar(f"falta {caminho}")
|
||||
pecas.append(HumanService.add_mhclo_asset(caminho, corpo, asset_type=tipo, subdiv_levels=0))
|
||||
return esqueleto, corpo, pecas
|
||||
|
||||
|
||||
def aplicar_modificadores(obj):
|
||||
"""Grava na malha as máscaras (ajudantes e faces do corpo sob a roupa) e tira subdivisões; só fica o Armature."""
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
if obj.data.shape_keys:
|
||||
obj.shape_key_add(name="_mistura", from_mix=True)
|
||||
for chave in list(obj.data.shape_keys.key_blocks)[:-1]:
|
||||
obj.shape_key_remove(chave)
|
||||
obj.shape_key_remove(obj.data.shape_keys.key_blocks[0])
|
||||
for mod in list(obj.modifiers):
|
||||
if mod.type == "ARMATURE":
|
||||
continue
|
||||
if mod.type == "SUBSURF":
|
||||
obj.modifiers.remove(mod)
|
||||
continue
|
||||
with bpy.context.temp_override(object=obj, active_object=obj):
|
||||
bpy.ops.object.modifier_apply(modifier=mod.name)
|
||||
|
||||
|
||||
def reduzir(malhas, alvo):
|
||||
"""Colapso de arestas com a mesma razão em todas as malhas (antes do Armature: pesos interpolados)."""
|
||||
total = sum(triangulos(m) for m in malhas)
|
||||
razao = alvo / total
|
||||
for _ in range(4):
|
||||
for m in malhas:
|
||||
mod = m.modifiers.new("reduzir", "DECIMATE")
|
||||
mod.ratio = min(1.0, razao)
|
||||
mod.use_collapse_triangulate = True
|
||||
m.modifiers.move(len(m.modifiers) - 1, 0)
|
||||
with bpy.context.temp_override(object=m, active_object=m):
|
||||
bpy.ops.object.modifier_apply(modifier=mod.name)
|
||||
total = sum(triangulos(m) for m in malhas)
|
||||
if total <= MAX_TRIS:
|
||||
return total
|
||||
razao = alvo / total
|
||||
falhar(f"redução parou em {total} triângulos")
|
||||
|
||||
|
||||
def comprimento_perna(esqueleto):
|
||||
ossos = esqueleto.pose.bones
|
||||
juntas = [esqueleto.matrix_world @ ossos["mixamorig:Left" + n].head for n in PERNA]
|
||||
return sum((b - a).length for a, b in zip(juntas, juntas[1:]))
|
||||
|
||||
|
||||
def contato(esqueleto):
|
||||
ossos = esqueleto.pose.bones
|
||||
pes = [f"mixamorig:{lado}{osso}" for lado in ("Left", "Right") for osso in ("Foot", "ToeBase")]
|
||||
return min((esqueleto.matrix_world @ ossos[n].head).z - ossos[n].bone.head_local.z for n in pes)
|
||||
|
||||
|
||||
def quadro_final(acao):
|
||||
fim = int(acao.frame_range[0])
|
||||
for curva in acao.layers[0].strips[0].channelbags[0].fcurves:
|
||||
pontos = curva.keyframe_points
|
||||
for anterior, ponto in zip(pontos, pontos[1:]):
|
||||
if abs(ponto.co[1] - anterior.co[1]) > 1e-4:
|
||||
fim = max(fim, round(ponto.co[0]))
|
||||
return fim
|
||||
|
||||
|
||||
def alinhar_rolagem(esqueleto, fonte):
|
||||
"""Gira cada osso do MPFB no próprio eixo até o Z dele apontar como o do osso do Mixamo no repouso.
|
||||
|
||||
A cópia de rotação é em espaço de mundo: com a rolagem igual, o osso novo fica com a direção e a torção do
|
||||
osso do clipe, mesmo com o repouso diferente (MPFB em A, Mixamo em T). A malha não muda: só o eixo do osso gira.
|
||||
"""
|
||||
bpy.context.view_layer.objects.active = esqueleto
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
para_local = esqueleto.matrix_world.inverted().to_3x3()
|
||||
for osso in esqueleto.data.edit_bones:
|
||||
if osso.name in fonte.data.bones:
|
||||
z = (fonte.matrix_world.to_3x3() @ fonte.data.bones[osso.name].matrix_local.to_3x3()).col[2]
|
||||
osso.align_roll(para_local @ z)
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
|
||||
def transferir(esqueleto, nome_clipe, arquivo, alinhar):
|
||||
"""Como em 3d/jogo.py: rotação de mundo de todos os ossos e posição só do quadril, escalada pela perna e com o pé
|
||||
de apoio no chão no primeiro quadro; assa a ação."""
|
||||
antes = set(bpy.data.objects)
|
||||
bpy.ops.import_scene.fbx(filepath=os.path.join(CLIPES_FBX, arquivo))
|
||||
novos = [o for o in bpy.data.objects if o not in antes]
|
||||
fonte = next(o for o in novos if o.type == "ARMATURE")
|
||||
if alinhar:
|
||||
alinhar_rolagem(esqueleto, fonte)
|
||||
acao_fonte = fonte.animation_data.action
|
||||
inicio, fim = int(acao_fonte.frame_range[0]), quadro_final(acao_fonte)
|
||||
bpy.context.scene.frame_set(inicio)
|
||||
fonte.scale *= comprimento_perna(esqueleto) / comprimento_perna(fonte)
|
||||
sem_par = []
|
||||
for pb in esqueleto.pose.bones:
|
||||
if pb.name not in fonte.pose.bones:
|
||||
sem_par.append(pb.name)
|
||||
continue
|
||||
c = pb.constraints.new("COPY_ROTATION")
|
||||
c.target, c.subtarget = fonte, pb.name
|
||||
if pb.name == QUADRIL:
|
||||
c = pb.constraints.new("COPY_LOCATION")
|
||||
c.target, c.subtarget = fonte, pb.name
|
||||
if sem_par:
|
||||
falhar(f"ossos do MPFB sem par no clipe: {sem_par}")
|
||||
bpy.context.scene.frame_set(inicio)
|
||||
fonte.location.z -= contato(esqueleto)
|
||||
bpy.context.view_layer.objects.active = esqueleto
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
bpy.ops.pose.select_all(action="SELECT")
|
||||
bpy.ops.nla.bake(frame_start=inicio, frame_end=fim, only_selected=True, visual_keying=True,
|
||||
clear_constraints=True, use_current_action=False, bake_types={"POSE"},
|
||||
channel_types={"LOCATION", "ROTATION"})
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
acao = esqueleto.animation_data.action
|
||||
acao.name = nome_clipe
|
||||
acao.use_fake_user = True
|
||||
curvas = acao.layers[0].strips[0].channelbags[0].fcurves
|
||||
for c in [c for c in curvas if c.data_path.endswith(".location") and f'["{QUADRIL}"]' not in c.data_path]:
|
||||
curvas.remove(c)
|
||||
ossos_fonte = fonte.data.bones.keys()
|
||||
for o in novos:
|
||||
bpy.data.objects.remove(o)
|
||||
bpy.data.actions.remove(acao_fonte)
|
||||
return ossos_fonte, inicio, fim
|
||||
|
||||
|
||||
def exportar(esqueleto, malhas):
|
||||
os.makedirs(RENDERS, exist_ok=True)
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
for o in [esqueleto, *malhas]:
|
||||
o.select_set(True)
|
||||
esqueleto.animation_data.action = None
|
||||
for acao in [a for a in bpy.data.actions if a.name not in CLIPES]:
|
||||
bpy.data.actions.remove(acao)
|
||||
bpy.context.scene.frame_set(1)
|
||||
bpy.ops.export_scene.gltf(filepath=GLB, export_format="GLB", use_selection=True, export_yup=True,
|
||||
export_apply=False, export_cameras=False, export_lights=False, export_morph=False,
|
||||
export_tangents=False, export_image_format="JPEG", export_jpeg_quality=80,
|
||||
export_animations=True, export_animation_mode="ACTIONS",
|
||||
export_anim_slide_to_zero=True, export_force_sampling=False,
|
||||
export_optimize_animation_size=True)
|
||||
|
||||
|
||||
def renderizar():
|
||||
"""Reabre o GLB e fotografa com a luz de renderizar() em 3d/jogo.py: parado no quadro 30 (frente e perto) e
|
||||
andando no meio do clipe."""
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
cena = bpy.context.scene
|
||||
bpy.ops.import_scene.gltf(filepath=GLB)
|
||||
esqueleto = next(o for o in cena.objects if o.type == "ARMATURE")
|
||||
malhas = [o for o in cena.objects if o.type == "MESH" and o.parent == esqueleto] # sem a esfera do importador
|
||||
pontos = [o.matrix_world @ v.co for o in malhas for v in o.data.vertices]
|
||||
pe, topo = min(p.z for p in pontos), max(p.z for p in pontos)
|
||||
mundo = bpy.data.worlds.new("neutro")
|
||||
mundo.use_nodes = True
|
||||
mundo.node_tree.nodes["Background"].inputs["Color"].default_value = (0.5, 0.5, 0.5, 1)
|
||||
mundo.node_tree.nodes["Background"].inputs["Strength"].default_value = 0.6
|
||||
cena.world = mundo
|
||||
luz = bpy.data.objects.new("principal", bpy.data.lights.new("principal", "AREA"))
|
||||
luz.data.energy, luz.data.size = 300, 2
|
||||
luz.location = (1.5, -3, 3)
|
||||
luz.rotation_euler = (Vector((0, 0, 1.2)) - luz.location).to_track_quat("-Z", "Y").to_euler()
|
||||
cena.collection.objects.link(luz)
|
||||
camera = bpy.data.objects.new("camera", bpy.data.cameras.new("camera"))
|
||||
cena.collection.objects.link(camera)
|
||||
cena.camera = camera
|
||||
ligar_cycles(cena)
|
||||
acoes = {a.name.split("_")[0]: a for a in bpy.data.actions}
|
||||
for nome, clipe, quadro, alvo, distancia in (("frente", "parado", 30, 0.9, 3.2), ("perto", "parado", 30, 1.4, 1.5),
|
||||
("andando", "andar", None, 0.9, 3.2)):
|
||||
acao = next(a for n, a in acoes.items() if n.startswith(clipe))
|
||||
esqueleto.animation_data.action = acao
|
||||
cena.frame_set(quadro or int(sum(acao.frame_range) / 2))
|
||||
camera.location = (0.3 * distancia / 4, -distancia, alvo + 0.1)
|
||||
camera.rotation_euler = (Vector((0, 0, alvo)) - camera.location).to_track_quat("-Z", "Y").to_euler()
|
||||
sair(cena, os.path.join(RENDERS, f"humano-{nome}.png"), (800, 800), "PNG", "RGB", 64, False)
|
||||
return pe, topo
|
||||
|
||||
|
||||
def main():
|
||||
dados = instalar_pacotes()
|
||||
esqueleto, corpo, pecas = criar(dados)
|
||||
roupas = [p for p, (tipo, _) in zip(pecas, PECAS) if tipo == "Clothes"]
|
||||
t_corpo_visivel = triangulos(corpo)
|
||||
for mod in corpo.modifiers: # mede o corpo sem as máscaras de roupa ("Delete" do MPFB)
|
||||
if mod.type == "MASK" and mod.vertex_group != "body":
|
||||
mod.show_viewport = False
|
||||
t_corpo = triangulos(corpo)
|
||||
for mod in corpo.modifiers:
|
||||
mod.show_viewport = True
|
||||
t_roupa = sum(triangulos(r) for r in roupas)
|
||||
t_resto = sum(triangulos(p) for p in pecas if p not in roupas)
|
||||
malhas = [corpo, *pecas]
|
||||
t_total = t_corpo + t_roupa + t_resto
|
||||
print(f"triângulos: corpo {t_corpo} (sem as faces sob a roupa: {t_corpo_visivel}), roupa {t_roupa}, "
|
||||
f"olhos+sobrancelha+cabelo {t_resto}, total {t_total}", flush=True)
|
||||
for m in malhas:
|
||||
aplicar_modificadores(m)
|
||||
t_escondido = sum(triangulos(m) for m in malhas)
|
||||
tris = reduzir(malhas, MAX_TRIS * 0.97)
|
||||
print(f"triângulos: total sem as faces escondidas {t_escondido}, reduzido {tris}", flush=True)
|
||||
for m in malhas:
|
||||
print(f" {m.name}: {triangulos(m)}", flush=True)
|
||||
ossos = [b.name for b in esqueleto.data.bones]
|
||||
print(f"ossos ({len(ossos)}): {' '.join(ossos)}", flush=True)
|
||||
for i, (nome, arquivo) in enumerate(CLIPES.items()):
|
||||
ossos_clipe, inicio, fim = transferir(esqueleto, nome, arquivo, alinhar=i == 0)
|
||||
print(f"clipe {nome} <- {arquivo} quadros {inicio}-{fim}; ossos do clipe sem par no MPFB: "
|
||||
f"{sorted(set(ossos_clipe) - set(ossos))}", flush=True)
|
||||
exportar(esqueleto, malhas)
|
||||
print(f"humano.glb {os.path.getsize(GLB)} bytes", flush=True)
|
||||
pe, topo = renderizar()
|
||||
print(f"repouso: pés em {pe:.3f} m, altura {topo - pe:.3f} m", flush=True)
|
||||
print(f"OK humano tris={tris} ossos={len(ossos)}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# Refaz public/jogo/itens.webp (atlas 2x2 de 512x512) a partir de 3d/itens/*.png.
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
FUZZ="${FUZZ:-6%}"
|
||||
Q="${Q:-90}"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
OUT=public/jogo/itens.webp
|
||||
CONF=/home/evandro/tmp/jogo
|
||||
mkdir -p "$CONF"
|
||||
|
||||
for n in municao kit cafe bateria; do
|
||||
# fundo por preenchimento a partir dos 4 cantos (sem limiar global)
|
||||
magick "3d/itens/$n.png" -alpha set -fuzz "$FUZZ" -fill none \
|
||||
-draw "alpha 0,0 floodfill" -draw "alpha 1023,0 floodfill" \
|
||||
-draw "alpha 0,1023 floodfill" -draw "alpha 1023,1023 floodfill" \
|
||||
-channel A -morphology Erode Disk:1 -blur 0x0.7 +channel \
|
||||
-trim +repage \
|
||||
-bordercolor none -background none -gravity center \
|
||||
-resize 215x215 \
|
||||
-extent 256x256 "$TMP/$n.png"
|
||||
done
|
||||
|
||||
magick -size 512x512 xc:none \
|
||||
"$TMP/municao.png" -geometry +0+0 -composite \
|
||||
"$TMP/kit.png" -geometry +256+0 -composite \
|
||||
"$TMP/cafe.png" -geometry +0+256 -composite \
|
||||
"$TMP/bateria.png" -geometry +256+256 -composite \
|
||||
-define webp:alpha-quality=100 -define webp:method=6 -quality "$Q" "$OUT"
|
||||
|
||||
magick -size 512x512 xc:"#808080" "$OUT" -composite "$CONF/itens-atlas-cinza.png"
|
||||
magick -size 512x512 xc:"#d00000" "$OUT" -composite "$CONF/itens-atlas-vermelho.png"
|
||||
ls -l "$OUT"
|
||||
|
After Width: | Height: | Size: 570 KiB |
|
After Width: | Height: | Size: 440 KiB |
|
After Width: | Height: | Size: 803 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,355 @@
|
||||
"""Mapa do escritório do jogo em /jogo: gera a primeira versão do .tmx do Tiled e converte o JSON exportado para a grade da lógica.
|
||||
|
||||
A fonte do mapa é o 3d/mapa/escritorio.tmx, editado à mão no Tiled. Rodar da raiz do repositório (o 3d/mapa.sh faz a cadeia toda):
|
||||
python3 3d/mapa.py gerar sobrescreve 3d/mapa/escritorio.tmx e 3d/mapa/ladrilhos.png pela semente, apagando as edições
|
||||
feitas no Tiled; só roda sob `bash 3d/mapa.sh --gerar`
|
||||
python3 3d/mapa.py converter lê 3d/mapa/escritorio.json (exportado pelo Tiled) e escreve lib/jogo/mapas/escritorio.ts
|
||||
|
||||
Coordenadas em células de 1 m: x cresce para a direita (leste), z para baixo (sul) no desenho; retângulos inclusivos.
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
from xml.sax.saxutils import escape, quoteattr
|
||||
|
||||
SEMENTE = 20261110
|
||||
LARGURA, ALTURA, LADO = 44, 32, 32
|
||||
RAIZ = Path(__file__).resolve().parent.parent
|
||||
PASTA = RAIZ / "3d" / "mapa"
|
||||
SAIDA_TS = RAIZ / "lib" / "jogo" / "mapas" / "escritorio.ts"
|
||||
|
||||
# nome, cor RGB, grade da lógica ("#" bloqueia passo e visão, "M" só o passo, "" é enfeite), cobre a célula inteira
|
||||
LADRILHOS = [
|
||||
("parede", (55, 58, 70), "#", True),
|
||||
("chão", (214, 206, 188), ".", True),
|
||||
("mesa", (176, 120, 64), "M", True),
|
||||
("cadeira", (40, 110, 200), "", False),
|
||||
("armário", (110, 140, 110), "#", True),
|
||||
("estante de servidor", (0, 150, 140), "#", True),
|
||||
("sofá", (190, 50, 60), "M", True),
|
||||
("bancada", (230, 180, 40), "M", True),
|
||||
("planta de vaso", (60, 160, 60), "", False),
|
||||
("bebedouro", (120, 200, 240), "", False),
|
||||
]
|
||||
GID = {nome: i + 1 for i, (nome, *_resto) in enumerate(LADRILHOS)}
|
||||
|
||||
PAREDES = [
|
||||
# borda
|
||||
(0, 0, 43, 0), (0, 31, 43, 31), (0, 0, 0, 31), (43, 0, 43, 31),
|
||||
# faixas de salas e anel do corredor
|
||||
(0, 7, 43, 7), (0, 24, 43, 24), (9, 7, 9, 24), (34, 7, 34, 24),
|
||||
(12, 10, 31, 10), (12, 21, 31, 21), (12, 10, 12, 21), (31, 10, 31, 21),
|
||||
# divisórias
|
||||
(15, 0, 15, 7), (28, 0, 28, 7), (0, 16, 9, 16), (34, 16, 43, 16), (17, 24, 17, 31), (24, 24, 24, 31),
|
||||
# poços dos elevadores, caixa da escada, cabines dos banheiros
|
||||
(1, 29, 3, 30), (6, 29, 8, 30), (21, 27, 23, 30), (1, 17, 2, 21),
|
||||
]
|
||||
|
||||
AMBIENTES = [
|
||||
("recepção", 1, 1, 14, 6),
|
||||
("sala de reunião", 16, 1, 27, 6),
|
||||
("diretoria", 29, 1, 42, 6),
|
||||
("copa", 1, 8, 8, 15),
|
||||
("banheiros", 1, 17, 8, 23),
|
||||
("área de mesas", 13, 11, 30, 20),
|
||||
("sala do servidor", 35, 8, 42, 15),
|
||||
("almoxarifado", 35, 17, 42, 23),
|
||||
("hall dos elevadores", 1, 25, 16, 30),
|
||||
("escada", 18, 25, 23, 30),
|
||||
("área de serviço", 25, 25, 42, 30),
|
||||
]
|
||||
|
||||
# trechos do anel, só para espalhar luminárias
|
||||
CORREDOR = [(10, 8, 33, 9), (10, 22, 33, 23), (10, 10, 11, 21), (32, 10, 33, 21)]
|
||||
|
||||
PORTAS = [
|
||||
("recepção – copa", 4, 7, 5, 7),
|
||||
("recepção – corredor", 12, 7, 13, 7),
|
||||
("sala de reunião – corredor", 18, 7, 19, 7),
|
||||
("sala de reunião – corredor", 24, 7, 25, 7),
|
||||
("sala de reunião – diretoria", 28, 3, 28, 4),
|
||||
("diretoria – corredor", 30, 7, 31, 7),
|
||||
("copa – corredor", 9, 11, 9, 12),
|
||||
("banheiros – corredor", 9, 19, 9, 20),
|
||||
("banheiros – hall dos elevadores", 4, 24, 5, 24),
|
||||
("hall dos elevadores – corredor", 13, 24, 14, 24),
|
||||
("hall dos elevadores – escada", 17, 27, 17, 28),
|
||||
("escada – corredor", 20, 24, 21, 24),
|
||||
("área de serviço – corredor", 28, 24, 29, 24),
|
||||
("área de serviço – almoxarifado", 38, 24, 39, 24),
|
||||
("almoxarifado – corredor", 34, 19, 34, 20),
|
||||
("sala do servidor – almoxarifado", 38, 16, 39, 16),
|
||||
("sala do servidor – corredor", 34, 11, 34, 12),
|
||||
("área de mesas – corredor norte", 21, 10, 22, 10),
|
||||
("área de mesas – corredor norte", 28, 10, 29, 10),
|
||||
("área de mesas – corredor sul", 21, 21, 22, 21),
|
||||
("área de mesas – corredor oeste", 12, 15, 12, 16),
|
||||
("área de mesas – corredor leste", 31, 15, 31, 16),
|
||||
]
|
||||
|
||||
ILHAS = [(15, 13, 19, 14), (23, 13, 27, 14), (15, 17, 19, 18), (23, 17, 27, 18)]
|
||||
|
||||
MOVEIS = [
|
||||
# recepção
|
||||
("sofá", 3, 1, 5, 1), ("sofá", 1, 3, 1, 6), ("bancada", 8, 3, 11, 3),
|
||||
("cadeira", 9, 2, 9, 2), ("cadeira", 10, 2, 10, 2), ("planta de vaso", 1, 1, 1, 1), ("bebedouro", 14, 1, 14, 1),
|
||||
# sala de reunião
|
||||
("mesa", 18, 3, 25, 4), ("cadeira", 17, 3, 17, 3), ("cadeira", 26, 4, 26, 4),
|
||||
("planta de vaso", 16, 1, 16, 1), ("planta de vaso", 27, 1, 27, 1),
|
||||
# diretoria
|
||||
("mesa", 35, 3, 38, 4), ("cadeira", 37, 2, 37, 2), ("cadeira", 36, 5, 36, 5), ("cadeira", 38, 5, 38, 5),
|
||||
("sofá", 29, 1, 31, 1), ("armário", 42, 1, 42, 4), ("planta de vaso", 41, 6, 41, 6),
|
||||
# copa
|
||||
("bancada", 1, 11, 1, 14), ("bancada", 1, 15, 4, 15), ("armário", 7, 15, 8, 15), ("mesa", 4, 11, 5, 12),
|
||||
("cadeira", 3, 11, 3, 11), ("cadeira", 6, 12, 6, 12), ("cadeira", 4, 10, 4, 10), ("cadeira", 5, 13, 5, 13),
|
||||
("bebedouro", 1, 8, 1, 8),
|
||||
# banheiros
|
||||
("bancada", 5, 17, 8, 17), ("planta de vaso", 8, 23, 8, 23),
|
||||
# hall dos elevadores
|
||||
("sofá", 11, 30, 14, 30), ("planta de vaso", 1, 25, 1, 25), ("bebedouro", 16, 25, 16, 25),
|
||||
# área de serviço
|
||||
("armário", 27, 30, 32, 30), ("bancada", 40, 27, 42, 28), ("bebedouro", 33, 25, 33, 25),
|
||||
# almoxarifado
|
||||
("armário", 42, 17, 42, 19), ("armário", 37, 20, 39, 20), ("armário", 35, 23, 36, 23),
|
||||
# sala do servidor
|
||||
("estante de servidor", 37, 10, 42, 10), ("estante de servidor", 37, 13, 42, 13),
|
||||
# área de mesas
|
||||
*[("mesa", *ilha) for ilha in ILHAS],
|
||||
("armário", 30, 11, 30, 13), ("planta de vaso", 13, 11, 13, 11), ("planta de vaso", 13, 20, 13, 20),
|
||||
("planta de vaso", 30, 20, 30, 20), ("bebedouro", 30, 14, 30, 14),
|
||||
# corredor
|
||||
("planta de vaso", 10, 8, 10, 8), ("planta de vaso", 33, 8, 33, 8),
|
||||
("planta de vaso", 10, 23, 10, 23), ("planta de vaso", 33, 23, 33, 23),
|
||||
]
|
||||
|
||||
INICIO = (7, 5)
|
||||
NASCEDOUROS = [
|
||||
("elevador 1", 2, 27), ("elevador 2", 7, 27), ("escada", 19, 29), ("banheiros", 6, 21),
|
||||
("fundos, área de serviço", 26, 29), ("fundos, área de serviço", 41, 30), ("fundos, almoxarifado", 41, 22),
|
||||
]
|
||||
|
||||
# célula de chão e para onde o cartaz olha; a parede fica do lado oposto
|
||||
CARTAZES = [
|
||||
(7, 1, "S"), (12, 1, "S"), (21, 1, "S"), (16, 4, "L"), (36, 1, "S"), (8, 14, "O"), (20, 9, "N"),
|
||||
(10, 18, "L"), (33, 14, "O"), (25, 22, "S"), (20, 11, "S"), (13, 18, "L"), (11, 25, "S"),
|
||||
]
|
||||
FACES = {"N": (0, 1), "S": (0, -1), "L": (-1, 0), "O": (1, 0)} # deslocamento até a parede
|
||||
|
||||
NOTAS = """Escritório do jogo em /jogo, 1 célula = 1 m. Este arquivo é a fonte do mapa: abra no Tiled (`flatpak run org.mapeditor.Tiled`), edite, salve e rode `bash 3d/mapa.sh`, que exporta o JSON, converte a grade da lógica e monta o 3D. `bash 3d/mapa.sh --gerar` refaz este arquivo pelo 3d/mapa.py e apaga as edições feitas aqui.
|
||||
Camadas: planta (parede e chão), moveis (a propriedade grade de cada ladrilho diz como entra na lógica: # bloqueia passo e visão, M bloqueia o passo e deixa o tiro passar, vazio é só enfeite), marcas (objetos pelo tipo).
|
||||
Tipos em marcas: inicio (ponto do jogador), nascedouro (ponto; o nome diz o lugar), ambiente (retângulo com o nome da sala), porta (retângulo sobre as células abertas na parede), luminaria (ponto no teto), cartaz (ponto na face da parede; face = para onde o cartaz olha: N para cima no desenho, S para baixo, L para a direita, O para a esquerda; imagem de 1 a 4)."""
|
||||
|
||||
|
||||
def celulas(x0, z0, x1, z1):
|
||||
return [(x, z) for z in range(z0, z1 + 1) for x in range(x0, x1 + 1)]
|
||||
|
||||
|
||||
def png(largura, altura, rgba):
|
||||
"""PNG RGBA 8 bits sem metadados (os mesmos pixels dão os mesmos bytes)."""
|
||||
linhas = b"".join(b"\0" + rgba[y * largura * 4 : (y + 1) * largura * 4] for y in range(altura))
|
||||
|
||||
def bloco(tipo, dados):
|
||||
return struct.pack(">I", len(dados)) + tipo + dados + struct.pack(">I", zlib.crc32(tipo + dados))
|
||||
|
||||
cabecalho = struct.pack(">IIBBBBB", largura, altura, 8, 6, 0, 0, 0)
|
||||
return b"\x89PNG\r\n\x1a\n" + bloco(b"IHDR", cabecalho) + bloco(b"IDAT", zlib.compress(linhas, 9)) + bloco(b"IEND", b"")
|
||||
|
||||
|
||||
def imagem_ladrilhos():
|
||||
"""Uma linha de ladrilhos de 32 px: cheios com borda escura; enfeites são um quadrado menor sobre fundo transparente."""
|
||||
largura = LADO * len(LADRILHOS)
|
||||
pixels = bytearray(largura * LADO * 4)
|
||||
for i, (_nome, cor, _grade, cheio) in enumerate(LADRILHOS):
|
||||
escura = tuple(c // 2 for c in cor)
|
||||
margem = 0 if cheio else 8
|
||||
for y in range(margem, LADO - margem):
|
||||
for x in range(margem, LADO - margem):
|
||||
borda = min(x - margem, y - margem, LADO - 1 - margem - x, LADO - 1 - margem - y) < 2
|
||||
p = (y * largura + i * LADO + x) * 4
|
||||
pixels[p : p + 4] = bytes((*(escura if borda else cor), 255))
|
||||
return png(largura, LADO, bytes(pixels))
|
||||
|
||||
|
||||
def montar_planta():
|
||||
planta = [["chão"] * LARGURA for _ in range(ALTURA)]
|
||||
for parede in PAREDES:
|
||||
for x, z in celulas(*parede):
|
||||
planta[z][x] = "parede"
|
||||
for _nome, *porta in PORTAS:
|
||||
for x, z in celulas(*porta):
|
||||
planta[z][x] = "chão"
|
||||
return planta
|
||||
|
||||
|
||||
def montar_moveis(rng):
|
||||
moveis = [[None] * LARGURA for _ in range(ALTURA)]
|
||||
for tipo, *retangulo in MOVEIS:
|
||||
for x, z in celulas(*retangulo):
|
||||
moveis[z][x] = tipo
|
||||
# cadeiras dos dois lados de cada ilha; algumas vagas ficam sem cadeira
|
||||
for x0, z0, x1, z1 in ILHAS:
|
||||
for x in range(x0, x1 + 1):
|
||||
for z in (z0 - 1, z1 + 1):
|
||||
if rng.random() < 0.8:
|
||||
moveis[z][x] = "cadeira"
|
||||
return moveis
|
||||
|
||||
|
||||
def objeto(id_, tipo, nome, x, y, largura=None, altura=None, propriedades=()):
|
||||
attrs = f'id="{id_}" name={quoteattr(nome)} type="{tipo}" x="{x:g}" y="{y:g}"'
|
||||
if largura is not None:
|
||||
attrs += f' width="{largura:g}" height="{altura:g}"'
|
||||
corpo = []
|
||||
if propriedades:
|
||||
corpo.append(" <properties>")
|
||||
corpo += [f' <property name="{n}" {t}value={quoteattr(str(v))}/>' for n, t, v in propriedades]
|
||||
corpo.append(" </properties>")
|
||||
if largura is None:
|
||||
corpo.append(" <point/>")
|
||||
return [f" <object {attrs}>", *corpo, " </object>"]
|
||||
|
||||
|
||||
def luminarias(x0, z0, x1, z1):
|
||||
"""Pontos a cada ~5 m dentro do retângulo, em pixels, arredondados à meia célula."""
|
||||
largura, altura = x1 - x0 + 1, z1 - z0 + 1
|
||||
nx, nz = max(1, round(largura / 5)), max(1, round(altura / 5))
|
||||
return [
|
||||
(round((x0 + largura * (i + 0.5) / nx) * 2) * LADO // 2, round((z0 + altura * (j + 0.5) / nz) * 2) * LADO // 2)
|
||||
for j in range(nz)
|
||||
for i in range(nx)
|
||||
]
|
||||
|
||||
|
||||
def gerar():
|
||||
rng = random.Random(SEMENTE)
|
||||
planta = montar_planta()
|
||||
moveis = montar_moveis(rng)
|
||||
marcas = []
|
||||
total = 0
|
||||
|
||||
def novo(*args, **kwargs):
|
||||
nonlocal total
|
||||
total += 1
|
||||
marcas.extend(objeto(total, *args, **kwargs))
|
||||
|
||||
novo("inicio", "inicio", (INICIO[0] + 0.5) * LADO, (INICIO[1] + 0.5) * LADO)
|
||||
for nome, x, z in NASCEDOUROS:
|
||||
novo("nascedouro", nome, (x + 0.5) * LADO, (z + 0.5) * LADO)
|
||||
for nome, x0, z0, x1, z1 in AMBIENTES:
|
||||
novo("ambiente", nome, x0 * LADO, z0 * LADO, (x1 - x0 + 1) * LADO, (z1 - z0 + 1) * LADO)
|
||||
for nome, x0, z0, x1, z1 in PORTAS:
|
||||
novo("porta", nome, x0 * LADO, z0 * LADO, (x1 - x0 + 1) * LADO, (z1 - z0 + 1) * LADO)
|
||||
for retangulo in [r[1:] for r in AMBIENTES] + CORREDOR:
|
||||
for x, y in luminarias(*retangulo):
|
||||
novo("luminaria", "luminaria", x, y)
|
||||
for x, z, face in CARTAZES:
|
||||
dx, dz = FACES[face]
|
||||
assert planta[z][x] == "chão" and planta[z + dz][x + dx] == "parede", (x, z, face)
|
||||
px = (x + 0.5 + dx / 2) * LADO
|
||||
pz = (z + 0.5 + dz / 2) * LADO
|
||||
propriedades = [("face", "", face), ("imagem", 'type="int" ', rng.randint(1, 4))]
|
||||
novo("cartaz", "cartaz", px, pz, propriedades=propriedades)
|
||||
|
||||
def csv(grade):
|
||||
return ",\n".join(",".join(str(GID[c] if c else 0) for c in linha) for linha in grade)
|
||||
|
||||
tiles = []
|
||||
for i, (nome, _cor, grade, _cheio) in enumerate(LADRILHOS):
|
||||
tiles += [
|
||||
f' <tile id="{i}">',
|
||||
" <properties>",
|
||||
f" <property name=\"nome\" value={quoteattr(nome)}/>",
|
||||
f' <property name="grade" value="{grade}"/>',
|
||||
" </properties>",
|
||||
" </tile>",
|
||||
]
|
||||
marcas_txt = "\n".join(marcas)
|
||||
tmx = "\n".join(
|
||||
[
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
f'<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="{LARGURA}" height="{ALTURA}" tilewidth="{LADO}" tileheight="{LADO}" infinite="0" nextlayerid="4" nextobjectid="{total + 1}">',
|
||||
" <properties>",
|
||||
f' <property name="notas">{escape(NOTAS)}</property>',
|
||||
f' <property name="semente" type="int" value="{SEMENTE}"/>',
|
||||
" </properties>",
|
||||
f' <tileset firstgid="1" name="ladrilhos" tilewidth="{LADO}" tileheight="{LADO}" tilecount="{len(LADRILHOS)}" columns="{len(LADRILHOS)}">',
|
||||
f' <image source="ladrilhos.png" width="{LADO * len(LADRILHOS)}" height="{LADO}"/>',
|
||||
*tiles,
|
||||
" </tileset>",
|
||||
f' <layer id="1" name="planta" width="{LARGURA}" height="{ALTURA}">',
|
||||
' <data encoding="csv">',
|
||||
csv(planta),
|
||||
"</data>",
|
||||
" </layer>",
|
||||
f' <layer id="2" name="moveis" width="{LARGURA}" height="{ALTURA}">',
|
||||
' <data encoding="csv">',
|
||||
csv(moveis),
|
||||
"</data>",
|
||||
" </layer>",
|
||||
' <objectgroup id="3" name="marcas">',
|
||||
marcas_txt,
|
||||
" </objectgroup>",
|
||||
"</map>",
|
||||
"",
|
||||
]
|
||||
)
|
||||
PASTA.mkdir(parents=True, exist_ok=True)
|
||||
(PASTA / "escritorio.tmx").write_text(tmx, encoding="utf-8")
|
||||
(PASTA / "ladrilhos.png").write_bytes(imagem_ladrilhos())
|
||||
|
||||
|
||||
def converter():
|
||||
dados = json.loads((PASTA / "escritorio.json").read_text(encoding="utf-8"))
|
||||
largura, altura, lado = dados["width"], dados["height"], dados["tilewidth"]
|
||||
grade_por_gid = {}
|
||||
for tileset in dados["tilesets"]:
|
||||
for tile in tileset.get("tiles", []):
|
||||
propriedades = {p["name"]: p["value"] for p in tile.get("properties", [])}
|
||||
grade_por_gid[tileset["firstgid"] + tile["id"]] = propriedades["grade"]
|
||||
camadas = {c["name"]: c for c in dados["layers"]}
|
||||
linhas = [["#"] * largura for _ in range(altura)]
|
||||
for nome in ("planta", "moveis"):
|
||||
for i, gid in enumerate(camadas[nome]["data"]):
|
||||
grade = grade_por_gid.get(gid & 0x1FFFFFFF, "") # bits altos: ladrilho espelhado ou girado
|
||||
if grade:
|
||||
linhas[i // largura][i % largura] = grade
|
||||
ambientes, portas = [], []
|
||||
for obj in camadas["marcas"]["objects"]:
|
||||
x, z = int(obj["x"] // lado), int(obj["y"] // lado)
|
||||
if obj["type"] in ("inicio", "nascedouro"):
|
||||
linhas[z][x] = "J" if obj["type"] == "inicio" else "N"
|
||||
elif obj["type"] in ("ambiente", "porta"):
|
||||
retangulo = {"x": x, "z": z, "largura": round(obj["width"] / lado), "altura": round(obj["height"] / lado)}
|
||||
if obj["type"] == "ambiente":
|
||||
ambientes.append({"nome": obj["name"], **retangulo})
|
||||
else:
|
||||
portas.append(retangulo)
|
||||
|
||||
def lista(itens):
|
||||
return "[\n" + "".join(f" {json.dumps(i, ensure_ascii=False)},\n" for i in itens) + "]"
|
||||
|
||||
retangulo_ts = "x: number; z: number; largura: number; altura: number"
|
||||
ts = f"""// Gerado por `bash 3d/mapa.sh` (`python3 3d/mapa.py converter`) a partir de 3d/mapa/escritorio.tmx: não edite à mão.
|
||||
|
||||
/** Planta do escritório para `lerMapa`: `#` parede ou móvel alto, `M` móvel baixo, `.` chão, `J` início, `N` nascedouro. */
|
||||
export const ESCRITORIO: readonly string[] = {lista("".join(linha) for linha in linhas)};
|
||||
|
||||
/** Salas com nome, em células (x para a direita, z para baixo). */
|
||||
export const AMBIENTES: readonly {{ nome: string; {retangulo_ts} }}[] = {lista(ambientes)};
|
||||
|
||||
/** Células abertas nas paredes, em células. */
|
||||
export const PORTAS: readonly {{ {retangulo_ts} }}[] = {lista(portas)};
|
||||
"""
|
||||
SAIDA_TS.parent.mkdir(parents=True, exist_ok=True)
|
||||
SAIDA_TS.write_text(ts, encoding="utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
comandos = {"gerar": gerar, "converter": converter}
|
||||
if len(sys.argv) != 2 or sys.argv[1] not in comandos:
|
||||
sys.exit("uso: python3 3d/mapa.py gerar|converter")
|
||||
comandos[sys.argv[1]]()
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Refaz o mapa do escritório a partir de 3d/mapa/escritorio.tmx, a fonte editada no Tiled: exporta JSON e PNG pelo
|
||||
# Tiled (sem janela), converte para a grade da lógica e monta o 3D no Blender. Rodar de qualquer lugar:
|
||||
# bash 3d/mapa.sh só exporta, converte e monta
|
||||
# bash 3d/mapa.sh --gerar antes, refaz o .tmx pelo 3d/mapa.py (apaga o que foi editado à mão no Tiled)
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
tiled() {
|
||||
env -u DISPLAY -u WAYLAND_DISPLAY flatpak run --env=QT_QPA_PLATFORM=offscreen --command="$1" org.mapeditor.Tiled "${@:2}"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
"") ;;
|
||||
--gerar)
|
||||
echo "aviso: --gerar sobrescreve 3d/mapa/escritorio.tmx pelo 3d/mapa.py; o que foi editado no Tiled se perde" >&2
|
||||
python3 3d/mapa.py gerar
|
||||
;;
|
||||
*)
|
||||
echo "uso: bash 3d/mapa.sh [--gerar]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
tiled tiled --export-map json "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.json"
|
||||
tiled tmxrasterizer "$PWD/3d/mapa/escritorio.tmx" "$PWD/3d/mapa/escritorio.png"
|
||||
python3 3d/mapa.py converter
|
||||
./node_modules/.bin/biome format --write lib/jogo/mapas/escritorio.ts 3d/mapa/escritorio.json
|
||||
blender -b -P 3d/mapa_3d.py
|
||||
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,477 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="44" height="32" tilewidth="32" tileheight="32" infinite="0" nextlayerid="4" nextobjectid="105">
|
||||
<properties>
|
||||
<property name="notas">Escritório do jogo em /jogo, 1 célula = 1 m. Este arquivo é a fonte do mapa: abra no Tiled (`flatpak run org.mapeditor.Tiled`), edite, salve e rode `bash 3d/mapa.sh`, que exporta o JSON, converte a grade da lógica e monta o 3D. `bash 3d/mapa.sh --gerar` refaz este arquivo pelo 3d/mapa.py e apaga as edições feitas aqui.
|
||||
Camadas: planta (parede e chão), moveis (a propriedade grade de cada ladrilho diz como entra na lógica: # bloqueia passo e visão, M bloqueia o passo e deixa o tiro passar, vazio é só enfeite), marcas (objetos pelo tipo).
|
||||
Tipos em marcas: inicio (ponto do jogador), nascedouro (ponto; o nome diz o lugar), ambiente (retângulo com o nome da sala), porta (retângulo sobre as células abertas na parede), luminaria (ponto no teto), cartaz (ponto na face da parede; face = para onde o cartaz olha: N para cima no desenho, S para baixo, L para a direita, O para a esquerda; imagem de 1 a 4).</property>
|
||||
<property name="semente" type="int" value="20261110"/>
|
||||
</properties>
|
||||
<tileset firstgid="1" name="ladrilhos" tilewidth="32" tileheight="32" tilecount="10" columns="10">
|
||||
<image source="ladrilhos.png" width="320" height="32"/>
|
||||
<tile id="0">
|
||||
<properties>
|
||||
<property name="nome" value="parede"/>
|
||||
<property name="grade" value="#"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="1">
|
||||
<properties>
|
||||
<property name="nome" value="chão"/>
|
||||
<property name="grade" value="."/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="2">
|
||||
<properties>
|
||||
<property name="nome" value="mesa"/>
|
||||
<property name="grade" value="M"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="3">
|
||||
<properties>
|
||||
<property name="nome" value="cadeira"/>
|
||||
<property name="grade" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="4">
|
||||
<properties>
|
||||
<property name="nome" value="armário"/>
|
||||
<property name="grade" value="#"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="5">
|
||||
<properties>
|
||||
<property name="nome" value="estante de servidor"/>
|
||||
<property name="grade" value="#"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="6">
|
||||
<properties>
|
||||
<property name="nome" value="sofá"/>
|
||||
<property name="grade" value="M"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="7">
|
||||
<properties>
|
||||
<property name="nome" value="bancada"/>
|
||||
<property name="grade" value="M"/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="8">
|
||||
<properties>
|
||||
<property name="nome" value="planta de vaso"/>
|
||||
<property name="grade" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
<tile id="9">
|
||||
<properties>
|
||||
<property name="nome" value="bebedouro"/>
|
||||
<property name="grade" value=""/>
|
||||
</properties>
|
||||
</tile>
|
||||
</tileset>
|
||||
<layer id="1" name="planta" width="44" height="32">
|
||||
<data encoding="csv">
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,1,2,2,1,1,1,1,1,1,2,2,1,1,1,1,2,2,1,1,1,1,2,2,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,2,2,1,1,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,1,1,1,1,
|
||||
1,1,1,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,2,2,2,2,2,2,1,2,2,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,1,2,2,1,1,1,1,1,1,1,2,2,1,1,1,1,1,2,2,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,2,2,1,1,1,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,1,2,2,1,1,1,2,2,2,2,2,2,2,2,1,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,1,2,2,1,1,1,2,2,2,2,2,2,2,2,1,2,2,2,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
</data>
|
||||
</layer>
|
||||
<layer id="2" name="moveis" width="44" height="32">
|
||||
<data encoding="csv">
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,9,0,7,7,7,0,0,0,0,0,0,0,0,10,0,9,0,0,0,0,0,0,0,0,0,0,9,0,7,7,7,0,0,0,0,0,0,0,0,0,0,5,0,
|
||||
0,0,0,0,0,0,0,0,0,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,5,0,
|
||||
0,7,0,0,0,0,0,0,8,8,8,8,0,0,0,0,0,4,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,3,3,3,3,0,0,0,5,0,
|
||||
0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,4,0,0,0,0,0,0,0,0,3,3,3,3,0,0,0,5,0,
|
||||
0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,0,0,0,0,0,
|
||||
0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,10,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6,0,
|
||||
0,8,0,4,3,3,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,8,0,0,3,3,4,0,0,0,0,0,0,0,0,4,0,4,4,4,0,0,0,4,0,4,0,4,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,8,0,0,0,4,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,5,0,0,0,0,0,0,6,6,6,6,6,6,0,
|
||||
0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,8,8,8,8,0,0,5,5,0,0,0,0,0,0,4,4,4,4,4,0,0,0,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,0,4,0,0,0,0,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,8,8,8,8,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,0,0,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,4,4,0,0,0,4,0,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,0,0,0,0,0,5,5,5,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,9,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,0,5,5,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,8,8,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
</data>
|
||||
</layer>
|
||||
<objectgroup id="3" name="marcas">
|
||||
<object id="1" name="inicio" type="inicio" x="240" y="176">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="2" name="elevador 1" type="nascedouro" x="80" y="880">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="3" name="elevador 2" type="nascedouro" x="240" y="880">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="4" name="escada" type="nascedouro" x="624" y="944">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="5" name="banheiros" type="nascedouro" x="208" y="688">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="6" name="fundos, área de serviço" type="nascedouro" x="848" y="944">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="7" name="fundos, área de serviço" type="nascedouro" x="1328" y="976">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="8" name="fundos, almoxarifado" type="nascedouro" x="1328" y="720">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="9" name="recepção" type="ambiente" x="32" y="32" width="448" height="192">
|
||||
</object>
|
||||
<object id="10" name="sala de reunião" type="ambiente" x="512" y="32" width="384" height="192">
|
||||
</object>
|
||||
<object id="11" name="diretoria" type="ambiente" x="928" y="32" width="448" height="192">
|
||||
</object>
|
||||
<object id="12" name="copa" type="ambiente" x="32" y="256" width="256" height="256">
|
||||
</object>
|
||||
<object id="13" name="banheiros" type="ambiente" x="32" y="544" width="256" height="224">
|
||||
</object>
|
||||
<object id="14" name="área de mesas" type="ambiente" x="416" y="352" width="576" height="320">
|
||||
</object>
|
||||
<object id="15" name="sala do servidor" type="ambiente" x="1120" y="256" width="256" height="256">
|
||||
</object>
|
||||
<object id="16" name="almoxarifado" type="ambiente" x="1120" y="544" width="256" height="224">
|
||||
</object>
|
||||
<object id="17" name="hall dos elevadores" type="ambiente" x="32" y="800" width="512" height="192">
|
||||
</object>
|
||||
<object id="18" name="escada" type="ambiente" x="576" y="800" width="192" height="192">
|
||||
</object>
|
||||
<object id="19" name="área de serviço" type="ambiente" x="800" y="800" width="576" height="192">
|
||||
</object>
|
||||
<object id="20" name="recepção – copa" type="porta" x="128" y="224" width="64" height="32">
|
||||
</object>
|
||||
<object id="21" name="recepção – corredor" type="porta" x="384" y="224" width="64" height="32">
|
||||
</object>
|
||||
<object id="22" name="sala de reunião – corredor" type="porta" x="576" y="224" width="64" height="32">
|
||||
</object>
|
||||
<object id="23" name="sala de reunião – corredor" type="porta" x="768" y="224" width="64" height="32">
|
||||
</object>
|
||||
<object id="24" name="sala de reunião – diretoria" type="porta" x="896" y="96" width="32" height="64">
|
||||
</object>
|
||||
<object id="25" name="diretoria – corredor" type="porta" x="960" y="224" width="64" height="32">
|
||||
</object>
|
||||
<object id="26" name="copa – corredor" type="porta" x="288" y="352" width="32" height="64">
|
||||
</object>
|
||||
<object id="27" name="banheiros – corredor" type="porta" x="288" y="608" width="32" height="64">
|
||||
</object>
|
||||
<object id="28" name="banheiros – hall dos elevadores" type="porta" x="128" y="768" width="64" height="32">
|
||||
</object>
|
||||
<object id="29" name="hall dos elevadores – corredor" type="porta" x="416" y="768" width="64" height="32">
|
||||
</object>
|
||||
<object id="30" name="hall dos elevadores – escada" type="porta" x="544" y="864" width="32" height="64">
|
||||
</object>
|
||||
<object id="31" name="escada – corredor" type="porta" x="640" y="768" width="64" height="32">
|
||||
</object>
|
||||
<object id="32" name="área de serviço – corredor" type="porta" x="896" y="768" width="64" height="32">
|
||||
</object>
|
||||
<object id="33" name="área de serviço – almoxarifado" type="porta" x="1216" y="768" width="64" height="32">
|
||||
</object>
|
||||
<object id="34" name="almoxarifado – corredor" type="porta" x="1088" y="608" width="32" height="64">
|
||||
</object>
|
||||
<object id="35" name="sala do servidor – almoxarifado" type="porta" x="1216" y="512" width="64" height="32">
|
||||
</object>
|
||||
<object id="36" name="sala do servidor – corredor" type="porta" x="1088" y="352" width="32" height="64">
|
||||
</object>
|
||||
<object id="37" name="área de mesas – corredor norte" type="porta" x="672" y="320" width="64" height="32">
|
||||
</object>
|
||||
<object id="38" name="área de mesas – corredor norte" type="porta" x="896" y="320" width="64" height="32">
|
||||
</object>
|
||||
<object id="39" name="área de mesas – corredor sul" type="porta" x="672" y="672" width="64" height="32">
|
||||
</object>
|
||||
<object id="40" name="área de mesas – corredor oeste" type="porta" x="384" y="480" width="32" height="64">
|
||||
</object>
|
||||
<object id="41" name="área de mesas – corredor leste" type="porta" x="992" y="480" width="32" height="64">
|
||||
</object>
|
||||
<object id="42" name="luminaria" type="luminaria" x="112" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="43" name="luminaria" type="luminaria" x="256" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="44" name="luminaria" type="luminaria" x="400" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="45" name="luminaria" type="luminaria" x="608" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="46" name="luminaria" type="luminaria" x="800" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="47" name="luminaria" type="luminaria" x="1008" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="48" name="luminaria" type="luminaria" x="1152" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="49" name="luminaria" type="luminaria" x="1296" y="128">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="50" name="luminaria" type="luminaria" x="96" y="320">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="51" name="luminaria" type="luminaria" x="224" y="320">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="52" name="luminaria" type="luminaria" x="96" y="448">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="53" name="luminaria" type="luminaria" x="224" y="448">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="54" name="luminaria" type="luminaria" x="96" y="656">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="55" name="luminaria" type="luminaria" x="224" y="656">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="56" name="luminaria" type="luminaria" x="480" y="432">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="57" name="luminaria" type="luminaria" x="640" y="432">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="58" name="luminaria" type="luminaria" x="768" y="432">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="59" name="luminaria" type="luminaria" x="928" y="432">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="60" name="luminaria" type="luminaria" x="480" y="592">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="61" name="luminaria" type="luminaria" x="640" y="592">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="62" name="luminaria" type="luminaria" x="768" y="592">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="63" name="luminaria" type="luminaria" x="928" y="592">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="64" name="luminaria" type="luminaria" x="1184" y="320">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="65" name="luminaria" type="luminaria" x="1312" y="320">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="66" name="luminaria" type="luminaria" x="1184" y="448">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="67" name="luminaria" type="luminaria" x="1312" y="448">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="68" name="luminaria" type="luminaria" x="1184" y="656">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="69" name="luminaria" type="luminaria" x="1312" y="656">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="70" name="luminaria" type="luminaria" x="112" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="71" name="luminaria" type="luminaria" x="288" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="72" name="luminaria" type="luminaria" x="464" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="73" name="luminaria" type="luminaria" x="672" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="74" name="luminaria" type="luminaria" x="864" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="75" name="luminaria" type="luminaria" x="1024" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="76" name="luminaria" type="luminaria" x="1152" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="77" name="luminaria" type="luminaria" x="1312" y="896">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="78" name="luminaria" type="luminaria" x="400" y="288">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="79" name="luminaria" type="luminaria" x="544" y="288">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="80" name="luminaria" type="luminaria" x="704" y="288">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="81" name="luminaria" type="luminaria" x="864" y="288">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="82" name="luminaria" type="luminaria" x="1008" y="288">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="83" name="luminaria" type="luminaria" x="400" y="736">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="84" name="luminaria" type="luminaria" x="544" y="736">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="85" name="luminaria" type="luminaria" x="704" y="736">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="86" name="luminaria" type="luminaria" x="864" y="736">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="87" name="luminaria" type="luminaria" x="1008" y="736">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="88" name="luminaria" type="luminaria" x="352" y="416">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="89" name="luminaria" type="luminaria" x="352" y="608">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="90" name="luminaria" type="luminaria" x="1056" y="416">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="91" name="luminaria" type="luminaria" x="1056" y="608">
|
||||
<point/>
|
||||
</object>
|
||||
<object id="92" name="cartaz" type="cartaz" x="240" y="32">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="1"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="93" name="cartaz" type="cartaz" x="400" y="32">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="1"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="94" name="cartaz" type="cartaz" x="688" y="32">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="3"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="95" name="cartaz" type="cartaz" x="512" y="144">
|
||||
<properties>
|
||||
<property name="face" value="L"/>
|
||||
<property name="imagem" type="int" value="3"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="96" name="cartaz" type="cartaz" x="1168" y="32">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="2"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="97" name="cartaz" type="cartaz" x="288" y="464">
|
||||
<properties>
|
||||
<property name="face" value="O"/>
|
||||
<property name="imagem" type="int" value="2"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="98" name="cartaz" type="cartaz" x="656" y="320">
|
||||
<properties>
|
||||
<property name="face" value="N"/>
|
||||
<property name="imagem" type="int" value="4"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="99" name="cartaz" type="cartaz" x="320" y="592">
|
||||
<properties>
|
||||
<property name="face" value="L"/>
|
||||
<property name="imagem" type="int" value="3"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="100" name="cartaz" type="cartaz" x="1088" y="464">
|
||||
<properties>
|
||||
<property name="face" value="O"/>
|
||||
<property name="imagem" type="int" value="2"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="101" name="cartaz" type="cartaz" x="816" y="704">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="1"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="102" name="cartaz" type="cartaz" x="656" y="352">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="2"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="103" name="cartaz" type="cartaz" x="416" y="592">
|
||||
<properties>
|
||||
<property name="face" value="L"/>
|
||||
<property name="imagem" type="int" value="1"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
<object id="104" name="cartaz" type="cartaz" x="368" y="800">
|
||||
<properties>
|
||||
<property name="face" value="S"/>
|
||||
<property name="imagem" type="int" value="3"/>
|
||||
</properties>
|
||||
<point/>
|
||||
</object>
|
||||
</objectgroup>
|
||||
</map>
|
||||
|
After Width: | Height: | Size: 468 B |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 522 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 553 KiB |