Closed Talon1024 closed 2 months ago
Very good that you are taking care of this.
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()
So, is this implemented yet? I can imagine there was already a commit fays/weeks ago that I have seen.
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.
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:
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.