Align original Half-Life office walls and ceiling with explicit room UVs
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
"""Extract original Half-Life office wall/ceiling textures without touching Steam files."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import pathlib
|
||||
import struct
|
||||
from PIL import Image
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('wad', type=pathlib.Path, help='Original valve/halflife.wad')
|
||||
args = parser.parse_args()
|
||||
project = pathlib.Path(__file__).resolve().parents[1]
|
||||
out = project / 'assets/textures/office'
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
wad = args.wad.read_bytes()
|
||||
assert wad[:4] == b'WAD3'
|
||||
count, directory = struct.unpack_from('<ii', wad, 4)
|
||||
wanted = {'FIFTIES_WALL14U': 'wall.png', 'FIFTIES_CEIL5': 'ceiling.png'}
|
||||
manifest = {'wad_sha256': hashlib.sha256(wad).hexdigest(), 'textures': {}}
|
||||
for i in range(count):
|
||||
offset, size, _, typ, compression, _, _, raw = struct.unpack_from('<iiiBBBB16s', wad, directory + 32*i)
|
||||
name = raw.split(b'\0')[0].decode().upper()
|
||||
if name not in wanted:
|
||||
continue
|
||||
assert not compression and typ == 0x43, name
|
||||
_, w, h, pixels, _, _, last = struct.unpack_from('<16s6I', wad, offset)
|
||||
palette = offset + last + (w//8)*(h//8)
|
||||
assert struct.unpack_from('<H', wad, palette)[0] == 256
|
||||
im = Image.frombytes('P', (w, h), wad[offset+pixels:offset+pixels+w*h])
|
||||
im.putpalette(wad[palette+2:palette+2+768])
|
||||
im.convert('RGB').save(out / wanted[name])
|
||||
manifest['textures'][name] = {'file': wanted[name], 'size': [w, h],
|
||||
'lump_sha256': hashlib.sha256(wad[offset:offset+size]).hexdigest()}
|
||||
assert set(manifest['textures']) == set(wanted)
|
||||
(out / 'source_manifest.json').write_text(json.dumps(manifest, indent=2) + '\n')
|
||||
print(out)
|
||||
@@ -0,0 +1,60 @@
|
||||
extends SceneTree
|
||||
const RoomScript = preload("res://scripts/room.gd")
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("run_test")
|
||||
|
||||
func run_test() -> void:
|
||||
var world = load("res://scenes/main.tscn").instantiate()
|
||||
root.add_child(world)
|
||||
var surfaces = get_nodes_in_group("office_surfaces")
|
||||
assert(surfaces.size() == 17) # sixteen wall segments + ceiling
|
||||
for surface in surfaces:
|
||||
assert(surface.mesh is ArrayMesh)
|
||||
var mat = surface.mesh.surface_get_material(0)
|
||||
assert(not mat.uv1_triplanar)
|
||||
assert(mat.albedo_texture.resource_path.contains("/office/"))
|
||||
var arrays = surface.mesh.surface_get_arrays(0)
|
||||
assert(arrays[Mesh.ARRAY_TEX_UV].size() == arrays[Mesh.ARRAY_VERTEX].size())
|
||||
print("PASS: 17 superfici con UV esplicite e texture office originali")
|
||||
var north := Vector3(0, 2.5, -9.15)
|
||||
var low := Vector3(0, 0.75, -9.15)
|
||||
var high := Vector3(0, 4.25, -9.15)
|
||||
for y in [0.0, 1.5, 2.5, 3.5, 5.0]:
|
||||
var p := Vector3(2, y, -9)
|
||||
assert(RoomScript.office_uv(p, north, "wall").is_equal_approx(RoomScript.office_uv(p, low, "wall")))
|
||||
assert(RoomScript.office_uv(p, north, "wall").is_equal_approx(RoomScript.office_uv(p, high, "wall")))
|
||||
assert(is_equal_approx(RoomScript.office_uv(Vector3(0, 2.5, -9), north, "wall").y, 0.5))
|
||||
assert(is_equal_approx(RoomScript.office_uv(Vector3(4, 2.5, -9), north, "wall").x - RoomScript.office_uv(Vector3(0, 2.5, -9), north, "wall").x, 1))
|
||||
print("PASS: finestre senza reset UV, stessa quota verticale, periodo parete 4m")
|
||||
var east := Vector3(9.15, 2.5, 0)
|
||||
var south := Vector3(0, 2.5, 9.15)
|
||||
var west := Vector3(-9.15, 2.5, 0)
|
||||
for pair in [[north, east, Vector3(9, 2.5, -9)], [east, south, Vector3(9, 2.5, 9)], [south, west, Vector3(-9, 2.5, 9)]]:
|
||||
assert(RoomScript.office_uv(pair[2], pair[0], "wall").is_equal_approx(RoomScript.office_uv(pair[2], pair[1], "wall")))
|
||||
var start := RoomScript.office_uv(Vector3(-9, 2.5, -9), north, "wall")
|
||||
var end := RoomScript.office_uv(Vector3(-9, 2.5, -9), west, "wall")
|
||||
assert(is_equal_approx(end.x - start.x, 18.0)) # full perimeter = integer repeats
|
||||
print("PASS: fase continua ai quattro angoli, chiusura perimetro intera")
|
||||
assert(RoomScript.office_uv(Vector3(-9, 5, -9), Vector3.ZERO, "ceiling") == Vector2.ZERO)
|
||||
assert(RoomScript.office_uv(Vector3(9, 5, 9), Vector3.ZERO, "ceiling").is_equal_approx(Vector2(15, 15)))
|
||||
assert(RoomScript.office_uv(Vector3(-7.8, 5, -7.8), Vector3.ZERO, "ceiling").is_equal_approx(Vector2.ONE))
|
||||
print("PASS: soffitto XZ, 15x15 ripetizioni, piastrelle da 60cm")
|
||||
if "--screenshot" in OS.get_cmdline_user_args():
|
||||
world.player.set_physics_process(false)
|
||||
world.player.position = Vector3(0, 0.1, 0)
|
||||
world.player.camera.rotation.x = 0.25
|
||||
var inspection := OmniLight3D.new()
|
||||
inspection.position = Vector3(0, 3, 0)
|
||||
inspection.omni_range = 30
|
||||
inspection.light_energy = 1.8
|
||||
world.add_child(inspection)
|
||||
for i in 3:
|
||||
world.player.rotation.y = -PI * 0.25 if i == 0 else PI * 0.25 + PI
|
||||
inspection.visible = i < 2 # last image retains actual horror lighting
|
||||
await create_timer(0.4).timeout
|
||||
await RenderingServer.frame_post_draw
|
||||
root.get_texture().get_image().save_png("/tmp/zombie-office-uv-%d.png" % i)
|
||||
world.queue_free()
|
||||
await process_frame
|
||||
quit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://8q5ib6vc01t0
|
||||
Reference in New Issue
Block a user