Realm667 / WolfenDoom

"WolfenDoom - Blade of Agony" | Important: This is only meant for development and testing purposes. You are NOT ALLOWED to use material from this repository for your own projects. Important: This repository is for development and testing purposes, you are NOT ALLOWED to use the copyrighted material for your own projects without our permission!
http://boa.realm667.com
254 stars 27 forks source link

Reworking debris piles #1320

Closed Talon1024 closed 2 months ago

Talon1024 commented 1 year ago

I think the debris piles need to be reworked. Some debris pieces on maps like C3M5_A just fall perpetually for some reason, plus all the debris pieces add to the total amount of draw calls and think times, which reduces performance (but by how much I don't know).

According to stat rendertimes, sprite/model rendering is the biggest bottleneck for BoA performance on maps like C3M0_A, so reworking/unifying the debris piles this way may noticeably improve performance.

I've been working on re-working/unifying the debris piles. Currently, I have imported all the debris models into a Blender file, and tried to port the debris spawning code from ZScript to Python. This is what I have so far:

Screenshot_20230630_221220

You can download the Blender file (for Blender 2.79b) here.

As you can see, I'm currently having trouble with rotating and positioning the debris pieces in a way that matches how they are oriented/positioned ingame.

Tormentor667 commented 1 year ago

Very good that you are taking care of this.

Talon1024 commented 1 year ago

The debris placement script for Blender is coming together. However, it seems to be placing too many debris pieces. I also need to account for debris variants and (possibly) texture replacements.

import bpy
from bpy import data as D, context as C
from collections import namedtuple
from random import randint, random
from mathutils import Vector
from math import pi, sin, cos, atan, atan2, radians
from operator import imul
from itertools import starmap

Vector2 = namedtuple("Vector2", "x y")
Vector2r = namedtuple("Vector2r", "min max mode")
Vector2rr = namedtuple("Vector2rr", "xmin xmax ymin ymax mode")
DropItem = namedtuple("DropItem", "actorclass probability amount")
DebrisInfo = namedtuple("DebrisInfo", "debrisbase doscale post_scale")
debris_items = tuple(starmap(DropItem,
    (("DebrisGirder", -2, 4),
    ("DebrisPipe", -2, 10),
    ("DebrisBeam", -10, 15),
    ("DebrisBrick", -10, 15),
    ("DebrisChunk", -10, 15),
    ("DebrisBottle", 0, 2),
    ("DebrisBottle2", 0, 2)),
))
default_debris_info = DebrisInfo(debrisbase=True, doscale=True, post_scale=Vector2(1.0, 1.0))
debris_info = {
    "DebrisChunk": default_debris_info._replace(doscale=False),
    "DebrisBrick": default_debris_info._replace(doscale=False),
    "DebrisBottle": default_debris_info._replace(doscale=False),
    "DebrisBottle2": default_debris_info._replace(doscale=False),
    "DebrisCorrugated": default_debris_info._replace(doscale=False),
    "DebrisPipe": default_debris_info._replace(
        post_scale=Vector2r(0.5, "maxscale", "multiply")),
    "DebrisBeam": default_debris_info._replace(
        post_scale=Vector2rr(0.75, "maxscale", 0.5, "maxscale", "multiply")),
    "DebrisGirder": default_debris_info._replace(
        post_scale=Vector2r(0.5, "min(maxscale, 2.5)", "multiply"))
}

ZObjectTransform = namedtuple("ZObjectTransform", "pos scale height radius")
default_debpiece_transform = ZObjectTransform(
    Vector((0.0,0.0,0.0)), Vector((1.0,1.0,1.0)), 4, 2)
debris_transforms = {
    "DebrisPipe": default_debpiece_transform._replace(radius=140),
    "DebrisBeam": default_debpiece_transform._replace(radius=128),
    "DebrisGirder": default_debpiece_transform._replace(radius=116),
    "DebrisBrick": default_debpiece_transform._replace(radius=14),
}

def scale_vector2(initial, scale, **kwargs):
    # GZDoom uses 2D vectors for scaling, since most of GZDoom's actors use
    # sprites for visuals
    op = None  # Just assign the new value
    # The strings may contain expressions to be evaluated
    eval_or_get = lambda v: \
        eval(v, globals(), kwargs) \
        if isinstance(v, str) \
        else v
    if isinstance(scale, Vector2):
        op = imul
    elif isinstance(scale, Vector2r):
        if scale.mode == "multiply":
            op = imul
        min_scale = eval_or_get(scale.min)
        max_scale = eval_or_get(scale.max)
        scale_value = random() * (max_scale - min_scale) + min_scale
        scale = Vector2(scale_value, scale_value)
    elif isinstance(scale, Vector2rr):
        if scale.mode == "multiply":
            op = imul
        min_x = eval_or_get(scale.xmin)
        max_x = eval_or_get(scale.xmax)
        min_y = eval_or_get(scale.ymin)
        max_y = eval_or_get(scale.ymax)
        scale_x = random() * (max_x - min_x) + min_x
        scale_y = random() * (max_y - min_y) + min_y
        scale = Vector2(scale_x, scale_y)
    # After op and scale are set
    if op is not None:
        initial.x = op(initial.x, scale.x)
        initial.y = op(initial.y, scale.x)
        initial.z = op(initial.z, scale.y)
    else:
        initial.x = scale.x
        initial.y = scale.x
        initial.z = scale.y
    return initial

# Ported from
# https://github.com/Realm667/WolfenDoom/blob/master/scripts/actors/debris.zs
def spawn_random(actor_obj, master_xf, self_xf, debris_info,
        probability = 255, amount = 1,
        distance = -1, min_distance = 0, spawned_count = 0):
    if amount <= 0: return None
    if distance < 0:
        distance = Radius
    for c in range(amount):
        if randint(0, 255) > probability:
            continue
        spawned_count += 1
        angle = random() * 2 * pi
        dist = min_distance + random() * (distance - min_distance)
        spawn_pos = Vector((sin(angle), cos(angle), 0.0)) * dist
        actor_mesh = actor_obj.data  # Corresponds to `type`
        debs_obj = spawn_thing(
            "debs{c:03d}".format(c=spawned_count), actor_mesh)
        debs_obj.location = spawn_pos

        pos_diff = spawn_pos - master_xf.pos

        # double dist = Distance2D(master);

        maxscale = 1.5
        # pitch = -atan((master.pos.z + master.Height - pos.z) / dist) + FRandom(-5, 15);
        # roll = Random(-30, 30);
        # angle = AngleTo(master) + Random(-35, 35);
        debs_obj.rotation_euler.x = -atan((
            master_xf.pos.z + master_xf.height - spawn_pos.z) / dist) + radians(
            random() * 20 - 5)
        debs_obj.rotation_euler.y = radians(random() * 60 - 30)
        debs_obj.rotation_euler.z = atan2(pos_diff.y, pos_diff.x) + radians(
            (random() * 70 - 35) + 90)

        obj_scale = debs_obj.scale.copy()
        if debris_info.debrisbase and debris_info.doscale:
            obj_scale = scale_vector2(
                obj_scale, Vector2r(0.25, 1.5, "multiply"))

        maxscale = (dist * 2.5) / (self_xf.radius * self_xf.scale.x)

        obj_scale = scale_vector2(
            obj_scale, debris_info.post_scale, maxscale=maxscale)
        debs_obj.scale = obj_scale
    C.scene.update()
    return spawned_count

def spawn_thing(thing_name, data):
    if not isinstance(thing_name, str):
        return False
    thing = D.objects.new(thing_name, data)
    C.scene.objects.link(thing)
    return thing

def spawn_items(
    pos=Vector((0.0,0.0,0.0)),
    scale=Vector((1.0,1.0,1.0)),
    radius=64,
    height=32
):
    scale.x = scale.x * radius / 24
    scale.y = scale.y * height / 40
    radius *= scale.x
    height *= scale.y
    spread = min(radius * 2, radius + 32)
    spawned_count = 0
    my_transform = ZObjectTransform(pos, scale, height, radius)
    for item in debris_items:
        item_obj = D.objects.get(item.actorclass, None)
        if item_obj is None: return None
        item_transform = debris_transforms.get(item.actorclass, default_debpiece_transform)
        item_info = debris_info.get(item.actorclass, default_debris_info)
        amount = int(max(item.amount, 1) * scale.x)
        probability = item.probability
        if probability < 1:
            problem = int(-probability * scale.x)
            amount = randint(problem, amount)
            probability = 255
        spawned_count = spawn_random(
            item_obj, my_transform, item_transform, item_info,
            probability, max(1, amount),
            spread * 1.1, spread * 0.75, spawned_count)

if __name__ == "__main__":
    spawn_items()
Tormentor667 commented 5 months ago

So, is this implemented yet? I can imagine there was already a commit fays/weeks ago that I have seen.

Talon1024 commented 4 months ago

Over the past few months, I've made progress on this. It's not implemented yet, but it's close.

I just updated the Blender file hosted on my personal website. You can download it again from the OP, or click this link.

The script used to place debris pieces is now included in the Blender file.

Also, the Blender file works best with Blender 2.79b, since that's the Blender version I used when working on it.