"""Seven people representatives for Champions of Luneraria, one weighted skeleton each: Mensch (Ritter), Elfe (Waldläuferin), Zwerg (Schildträger), Troll (Steinhauer), Fee (Lichtheilerin), Ork (Kriegshäuptling), Biestmensch (Klauenjäger). Built like the Funkenlanze (phase 18): continuous deforming limbs and torso, bevelled armour plates, rings, shards and closed hands. Hands hold their weapons through analytic two-bone arm IK solved per sample and baked to FK, so grips never slide. Attack contact is 0.90 s as SkeletalFigure expects. LP_* materials carry a SurfaceColor vertex layer for the shared runtime surface shader. Colours are authored as display (sRGB) values and stored linear. Run: ALSOFT_DRIVERS=null blender -b --python-exit-code 1 --python assets-source/luneraria/build_peoples.py [-- p_elf ...] Blender Z-up, figures face -Y (glTF +Z forward). Rigify side convention: .L at +X. """ from pathlib import Path import bpy, bmesh, json, hashlib, struct, sys from math import sin, cos, pi, sqrt from mathutils import Vector, Quaternion P = Path(__file__).resolve().parents[2] SRC = Path(__file__).resolve().parent OUT = P / 'assets/luneraria/peoples' OUT.mkdir(parents=True, exist_ok=True) (SRC / 'peoples').mkdir(exist_ok=True) CLIPS = {'idle': (3.0, True), 'start': (.4, False), 'walk': (1.1, True), 'stop': (.4, False), 'attack_a': (1.3, False), 'attack_b': (1.3, False), 'hit': (.5, False), 'ability': (1.3, False), 'death': (2.0, False)} CONTACT = .90 SIDES = (('L', 1), ('R', -1)) V = Vector KIND_NAMES = {'skin': 'Skin', 'cloth': 'Cloth', 'cloth2': 'ClothAccent', 'leather': 'Leather', 'metal': 'Metal', 'trim': 'TrimMetal', 'dark': 'Dark', 'hair': 'Hair', 'wood': 'Wood', 'stone': 'Stone', 'bone': 'Bone', 'fur': 'Fur', 'fur2': 'FurLight', 'glow': 'Glow', 'eye': 'EyeGlow'} # Palettes: display colour, roughness, metallic, emission. BASE = {'dark': ((.07, .065, .07), .7, .1, 0), 'bone': ((.86, .82, .70), .55, 0, 0), 'leather': ((.40, .27, .17), .78, 0, 0), 'wood': ((.42, .27, .15), .82, 0, 0), 'stone': ((.46, .45, .44), .9, 0, 0)} PEOPLES = { 'p_mensch': {'tag': 'Mensch', 'name': 'Ritter', 'people': 'Menschen', 'klasse': 'Ritter', 'rolle': 'front', 's': 1.0, 'w': 1.02, 'd': 1.0, 'limb': 1.05, 'arm': 1.0, 'leg': 1.0, 'head': 1.0, 'style': 'blade_shield', 'weapon': 'sword', 'shield': 'kite', 'pal': {'skin': ((.86, .68, .56), .6, 0, 0), 'cloth': ((.16, .26, .60), .86, 0, 0), 'cloth2': ((.90, .88, .82), .86, 0, 0), 'metal': ((.72, .74, .78), .3, .85, 0), 'trim': ((.88, .68, .30), .32, .8, 0)}}, 'p_elf': {'tag': 'Elf', 'name': 'Waldläuferin', 'people': 'Elfen', 'klasse': 'Waldläuferin', 'rolle': 'ranged', 's': 1.06, 'w': .84, 'd': .9, 'limb': .82, 'arm': 1.02, 'leg': 1.1, 'head': .94, 'style': 'bow', 'pal': {'skin': ((.86, .70, .58), .55, 0, 0), 'cloth': ((.20, .44, .26), .86, 0, 0), 'cloth2': ((.12, .26, .16), .88, 0, 0), 'trim': ((.78, .74, .52), .3, .75, 0), 'hair': ((.84, .66, .36), .7, 0, 0), 'glow': ((.55, 1.0, .62), .3, 0, 1.6), 'leather': ((.46, .33, .20), .76, 0, 0)}}, 'p_zwerg': {'tag': 'Zwerg', 'name': 'Schildträger', 'people': 'Zwerge', 'klasse': 'Schildträger', 'rolle': 'front', 's': .78, 'w': 1.34, 'd': 1.22, 'limb': 1.32, 'arm': .9, 'leg': .78, 'head': 1.2, 'style': 'blade_shield', 'weapon': 'hammer', 'shield': 'round', 'pal': {'skin': ((.84, .60, .48), .6, 0, 0), 'cloth': ((.50, .13, .10), .86, 0, 0), 'metal': ((.52, .52, .56), .34, .82, 0), 'trim': ((.82, .54, .22), .32, .8, 0), 'hair': ((.74, .33, .12), .72, 0, 0), 'glow': ((.40, .86, 1.0), .3, 0, 1.8), 'cloth2': ((.30, .10, .08), .86, 0, 0)}}, 'p_troll': {'tag': 'Troll', 'name': 'Steinhauer', 'people': 'Trolle', 'klasse': 'Steinhauer', 'rolle': 'splash', 's': 1.3, 'w': 1.42, 'd': 1.3, 'limb': 1.5, 'arm': 1.25, 'leg': .86, 'head': .92, 'hunch': .42, 'style': 'club2h', 'pal': {'skin': ((.40, .54, .58), .72, 0, 0), 'leather': ((.44, .34, .24), .82, 0, 0), 'eye': ((1.0, .80, .30), .3, 0, 2.2), 'hair': ((.20, .22, .20), .8, 0, 0), 'stone': ((.50, .50, .52), .92, 0, 0)}}, 'p_fee': {'tag': 'Fee', 'name': 'Lichtheilerin', 'people': 'Feen', 'klasse': 'Lichtheilerin', 'rolle': 'support', 's': .7, 'w': .78, 'd': .82, 'limb': .74, 'arm': 1.0, 'leg': 1.06, 'head': 1.14, 'hover': .32, 'style': 'staff', 'pal': {'skin': ((.90, .76, .72), .5, 0, 0), 'cloth': ((.62, .46, .88), .8, 0, 0), 'cloth2': ((.92, .90, .96), .8, 0, 0), 'trim': ((.94, .86, .60), .3, .75, 0), 'hair': ((.99, .78, .90), .7, 0, 0), 'glow': ((.62, .95, 1.0), .3, 0, 1.5), 'wood': ((.78, .66, .48), .7, 0, 0)}}, 'p_ork': {'tag': 'Ork', 'name': 'Kriegshäuptling', 'people': 'Orks', 'klasse': 'Kriegshäuptling', 'rolle': 'melee', 's': 1.12, 'w': 1.3, 'd': 1.16, 'limb': 1.3, 'arm': 1.06, 'leg': .95, 'head': 1.04, 'hunch': .16, 'style': 'axe', 'pal': {'skin': ((.42, .58, .28), .66, 0, 0), 'metal': ((.38, .37, .37), .38, .8, 0), 'trim': ((.66, .14, .10), .8, 0, 0), 'hair': ((.10, .09, .08), .8, 0, 0), 'fur': ((.36, .28, .20), .95, 0, 0), 'leather': ((.36, .23, .15), .8, 0, 0)}}, 'p_biest': {'tag': 'Biest', 'name': 'Klauenjäger', 'people': 'Biestmenschen', 'klasse': 'Klauenjäger', 'rolle': 'melee', 's': 1.1, 'w': 1.12, 'd': 1.1, 'limb': 1.18, 'arm': 1.08, 'leg': 1.02, 'head': 1.06, 'hunch': .22, 'style': 'claws', 'digitigrade': True, 'pal': {'fur': ((.56, .38, .24), .95, 0, 0), 'fur2': ((.82, .72, .56), .95, 0, 0), 'cloth': ((.18, .44, .44), .86, 0, 0), 'metal': ((.62, .46, .28), .34, .78, 0), 'eye': ((1.0, .70, .20), .3, 0, 2.2), 'skin': ((.30, .22, .18), .7, 0, 0)}}, } def lin(c): return tuple(x ** 2.2 for x in c) def blend(a, b, t): t = max(0.0, min(1.0, t)) return {a: 1 - t, b: t} if a != b else {a: 1} def catmull(points, t): seg = min(len(points) - 2, int(t * (len(points) - 1))) u = t * (len(points) - 1) - seg a, b, c, d = [points[max(0, min(len(points) - 1, q))] for q in (seg - 1, seg, seg + 1, seg + 2)] return .5 * ((2 * b) + (-a + c) * u + (2 * a - 5 * b + 4 * c - d) * u * u + (-a + 3 * b - 3 * c + d) * u * u * u) def curve(t, keys): for (a, x), (b, y) in zip(keys, keys[1:]): if t <= b: u = max(0, (t - a) / (b - a)) u = u * u * (3 - 2 * u) return x * (1 - u) + y * u return keys[-1][1] def ik_elbow(a, c, l1, l2, pole): """Two-bone solve; an unreachable target is clamped and the shortfall returned.""" delta = c - a d = delta.length error = 0.0 hi, lo = l1 + l2 - 1e-4, abs(l1 - l2) + 1e-4 if d > hi: error, c, d = d - hi, a + delta.normalized() * hi, hi elif d < lo: error, c, d = lo - d, a + delta.normalized() * lo, lo line = (c - a) / d v = V(pole) - a perpendicular = (v - line * v.dot(line)).normalized() along = (l1 * l1 - l2 * l2 + d * d) / (2 * d) return a + line * along + perpendicular * sqrt(max(0, l1 * l1 - along * along)), c, error def frame(u, hint): u = V(u).normalized() f = V(hint) - u * V(hint).dot(u) f.normalize() return u, f, u.cross(f) class Figure: def __init__(self, key, sp): self.key, self.sp = key, sp self.parts, self.defs, self.mats, self.errors = [], {}, {}, [] self.s = sp['s'] self.pal = dict(BASE) self.pal.update(sp['pal']) self.ankle = .085 self.hip = self.ankle + .84 * sp['leg'] self.knee = self.ankle + .84 * sp['leg'] * .52 self.waist = self.hip + .13 self.chest = self.hip + .38 self.shoulder = self.hip + .52 self.neck = self.hip + .60 self.head_c = self.neck + .10 + .085 * sp['head'] self.hover = sp.get('hover', 0.0) self.extra_bones = [] self.scaled = set() # ------------------------------------------------------------ space def X(self, p): """Pre-space (upright human proportions) -> final space: bend, scale, hover.""" x, y, z = p a = self.sp.get('hunch', 0.0) if a and z > self.waist: t = min(1.0, (z - self.waist) / (self.shoulder - self.waist)) ang = a * t * t * (3 - 2 * t) dz = z - self.waist y, z = y * cos(ang) - dz * sin(ang), self.waist + y * sin(ang) + dz * cos(ang) return V((x * self.s, y * self.s, z * self.s + self.hover)) # ------------------------------------------------------------ materials and meshes def mat(self, kind): name = 'LP_%s_%s' % (self.sp['tag'], KIND_NAMES[kind]) if name in self.mats: return self.mats[name] color, rough, metal, emission = self.pal[kind] color = lin(color) m = bpy.data.materials.new(name) m.diffuse_color = (*color, 1) m.use_nodes = True n, links = m.node_tree.nodes, m.node_tree.links bs = n.get('Principled BSDF') bs.inputs['Base Color'].default_value = (*color, 1) bs.inputs['Metallic'].default_value = metal bs.inputs['Roughness'].default_value = rough if not emission: vertex = n.new('ShaderNodeVertexColor') vertex.layer_name = 'SurfaceColor' links.new(vertex.outputs['Color'], bs.inputs['Base Color']) if emission: bs.inputs['Emission Color'].default_value = (*color, 1) bs.inputs['Emission Strength'].default_value = emission else: tex = n.new('ShaderNodeTexNoise') tex.inputs['Scale'].default_value = 65 if metal else 30 bump = n.new('ShaderNodeBump') bump.inputs['Strength'].default_value = .12 if metal else .2 bump.inputs['Distance'].default_value = .006 links.new(tex.outputs['Fac'], bump.inputs['Height']) links.new(bump.outputs['Normal'], bs.inputs['Normal']) self.mats[name] = m return m def mesh(self, name, vs, fs, kind, bone, weights=None, smooth=True, uv=None, pre=True): vs = [self.X(v) if pre else V(v) for v in vs] me = bpy.data.meshes.new(name) me.from_pydata([tuple(v) for v in vs], [], fs) me.update() ob = bpy.data.objects.new(name, me) bpy.context.collection.objects.link(ob) self.parts.append(ob) m = self.mat(kind) me.materials.append(m) col = me.color_attributes.new(name='SurfaceColor', type='FLOAT_COLOR', domain='CORNER') tex = me.uv_layers.new(name='UVMap') base = m.diffuse_color[:3] for face in me.polygons: face.use_smooth = smooth for li in face.loop_indices: vi = me.loops[li].vertex_index x, y, z = vs[vi] variation = .93 + .05 * sin(17 * x + 11 * z) + .025 * cos(24 * y - 7 * z) col.data[li].color = (*(min(1, c * variation) for c in base), 1) tex.data[li].uv = uv[vi] if uv else (x * 1.7 + y * .3, z * 1.5) groups = {} for i in range(len(vs)): ws = weights[i] if weights else {bone: 1.0} total = sum(ws.values()) for key, weight in ws.items(): if weight <= 1e-8: continue groups.setdefault(key, ob.vertex_groups.get(key) or ob.vertex_groups.new(name=key)) groups[key].add([i], weight / total, 'REPLACE') return ob def tube(self, name, points, radii, kind, bone, sides=16, steps=20, flatten=1.0, weight_fn=None, groove=0.0, smooth=True, pre=True, ref=None, lobes=5): pts = [V(x) for x in points] vs, fs, ws, uv = [], [], [], [] for j in range(steps + 1): t = j / steps p = catmull(pts, t) seg = min(len(radii) - 2, int(t * (len(radii) - 1))) a = t * (len(radii) - 1) - seg radius = radii[seg] * (1 - a) + radii[seg + 1] * a tangent = (catmull(pts, min(1, t + .002)) - catmull(pts, max(0, t - .002))).normalized() r = V(ref) if ref else (V((0, 1, 0)) if abs(tangent.y) < .9 else V((1, 0, 0))) u = tangent.cross(r).normalized() v = tangent.cross(u) for k in range(sides): angle = 2 * pi * k / sides detail = 1 + groove * cos(lobes * angle + t * 4) vs.append(p + radius * detail * (u * cos(angle) + v * sin(angle) * flatten)) ws.append(weight_fn(t) if weight_fn else {bone: 1}) uv.append((k / sides, t)) if j: for k in range(sides): a0, b0 = (j - 1) * sides + k, (j - 1) * sides + (k + 1) % sides fs.append((a0, b0, b0 + sides, a0 + sides)) fs += [tuple(reversed(range(sides))), tuple(range(steps * sides, (steps + 1) * sides))] return self.mesh(name, vs, fs, kind, bone, ws, smooth, uv, pre) def ell(self, name, c, r, kind, bone, seg=18, rings=11, smooth=True, pre=True, axes=None, bumps=0.0, weights=None): ax = [V(a).normalized() for a in axes] if axes else [V((1, 0, 0)), V((0, 1, 0)), V((0, 0, 1))] c = V(c) def at(th, a): w = 1 + bumps * sin(3 * a + 5 * th) * sin(2 * th) return c + ax[0] * r[0] * sin(th) * cos(a) * w + ax[1] * r[1] * sin(th) * sin(a) * w + ax[2] * r[2] * cos(th) * w vs = [c + ax[2] * r[2]] for j in range(1, rings): for k in range(seg): vs.append(at(pi * j / rings, 2 * pi * k / seg)) vs.append(c - ax[2] * r[2]) fs = [(0, 1 + k, 1 + (k + 1) % seg) for k in range(seg)] for j in range(rings - 2): for k in range(seg): a0 = 1 + j * seg + k b0 = 1 + j * seg + (k + 1) % seg fs.append((a0, a0 + seg, b0 + seg, b0)) fs += [(len(vs) - 1, len(vs) - 2 - k, len(vs) - 2 - (k + 1) % seg) for k in range(seg)] ws = [weights(v) for v in vs] if weights else None return self.mesh(name, vs, fs, kind, bone, ws, smooth, None, pre) def plate(self, name, outline, center, depth, kind, bone, normal=(0, -1, 0), pre=True, weights=None): """Closed tapered plate with bevel belt and a proud central ridge, never a box.""" c, n = V(center), V(normal).normalized() front = [V(p) for p in outline] vs = front + [p.lerp(c, .13) + n * depth * .32 for p in front] + [c + n * depth] + [p - n * depth * .22 for p in front] k = len(front) fs = [] for i in range(k): q = (i + 1) % k fs += [(i, q, k + q, k + i), (k + i, k + q, 2 * k), (i, 2 * k + 1 + i, 2 * k + 1 + q, q)] fs.append(tuple(reversed(range(2 * k + 1, 3 * k + 1)))) ws = [weights(v) for v in vs] if weights else None return self.mesh(name, vs, fs, kind, bone, ws, False, None, pre) def ring(self, name, p, axis, radius, width, kind, bone, sides=16, pre=True): axis, p = V(axis).normalized(), V(p) return self.tube(name, [p - axis * width / 2, p + axis * width / 2], [radius, radius], kind, bone, sides, 3, smooth=False, pre=pre) def shard(self, name, points, radius, kind, bone, pre=True, sides=6): return self.tube(name, points, [radius, .74 * radius, .002], kind, bone, sides, 5, smooth=False, pre=pre) def cone(self, name, base, tip, r, kind, bone, sides=8, pre=True): return self.tube(name, [base, V(base).lerp(V(tip), .5), tip], [r, r * .55, .0015], kind, bone, sides, 6, smooth=True, pre=pre) def loft(self, name, sections, kind, weight_fn, sides=24, steps=36, front_bulge=0.0, y0=0.0, smooth=True, scallop=0.0): """Elliptic loft in pre-space. sections: (z, rx, ry); front_bulge rounds the chest.""" vs, fs, ws = [], [], [] for j in range(steps + 1): t = j / steps f = t * (len(sections) - 1) idx = min(len(sections) - 2, int(f)) u = f - idx z, rx, ry = [sections[idx][q] * (1 - u) + sections[idx + 1][q] * u for q in range(3)] for k in range(sides): a = 2 * pi * k / sides bulge = 1 + front_bulge * max(0.0, -sin(a)) ** 2 * abs(cos(a)) * 1.6 edge = 1 + scallop * (cos(a * 9) * .5 + .5) * t vs.append((rx * cos(a) * edge, y0 + ry * sin(a) * bulge * edge, z)) ws.append(weight_fn(z, cos(a) * rx)) if j: for k in range(sides): a0, b0 = (j - 1) * sides + k, (j - 1) * sides + (k + 1) % sides fs.append((a0, b0, b0 + sides, a0 + sides)) fs += [tuple(reversed(range(sides))), tuple(range(steps * sides, (steps + 1) * sides))] return self.mesh(name, vs, fs, kind, 'Spine', ws, smooth) # ------------------------------------------------------------ weights def torso_weights(self, z, x=0.0): if z < self.waist - .05: return {'Pelvis': 1} if z < self.waist + .07: return blend('Pelvis', 'Spine', (z - self.waist + .05) / .12) if z < self.chest - .06: return {'Spine': 1} if z < self.chest + .05: return blend('Spine', 'Chest', (z - self.chest + .06) / .11) if z < self.neck - .02: return {'Chest': 1} return blend('Chest', 'Neck', (z - self.neck + .02) / .06) def skirt_weights(self, top, bottom, share=.45): """Hanging cloth: pelvis at the belt, partly following the nearer thigh below.""" def fn(z, x): t = max(0.0, min(1.0, (top - z) / max(.01, top - bottom))) side = 'Thigh.L' if x > 0 else 'Thigh.R' return {'Pelvis': 1 - share * t, side: share * t} return fn # ------------------------------------------------------------ body def build_body(self, torso_kind='skin', arm_kind='skin', leg_kind='cloth', boot_kind='leather', chest=1.0, belly=1.0): sp = self.sp w, d, lb, H = sp['w'], sp['d'], sp['limb'], self.hip secs = [(-.08, .14, .105), (0, .165, .118), (.06, .158, .112 * belly), (.13, .142, .104 * belly), (.22, .158, .112 * belly), (.32, .19 * chest, .124 * chest), (.42, .212 * chest, .128 * chest), (.49, .205 * chest, .118), (.55, .15, .095), (.60, .072, .066)] secs = [(H + z, rx * w, ry * d) for z, rx, ry in secs] self.loft('Torso', secs, torso_kind, self.torso_weights, front_bulge=.06) self.torso_sections = secs self.tube('Neck', [(0, .0, self.neck - .03), (0, -.004, self.neck + .05), (0, -.012, self.head_c - .05)], [.058 * lb, .054 * lb, .05 * lb], torso_kind if torso_kind not in ('metal', 'cloth', 'cloth2', 'leather') else 'skin', 'Neck', 16, 10, weight_fn=lambda t: blend('Chest', 'Neck', t * 2) if t < .5 else blend('Neck', 'Head', (t - .5) * 2)) # Arms in final space from IK against the style's grips. s = self.s self.l1, self.l2 = .285 * sp['arm'] * s, .265 * sp['arm'] * s self.L = self.l1 + self.l2 self.sh = {side: self.X((sx * .19 * w, .012, self.shoulder - .015)) for side, sx in SIDES} self.bind_grips() self.elbow, self.W = {}, {} for side, sx in SIDES: g = self.G[side] e0, _, _ = ik_elbow(self.sh[side], g, self.l1, self.l2, self.pole[side]) fore = (g - e0).normalized() w0 = g - fore * .035 * s * lb elbow, w0, err = ik_elbow(self.sh[side], w0, self.l1, self.l2, self.pole[side]) self.elbow[side], self.W[side] = elbow, w0 a, b, c = self.sh[side], elbow, w0 upper, fore_b, hand = 'UpperArm.' + side, 'Forearm.' + side, 'Hand.' + side def arm_weights(t, upper=upper, fore_b=fore_b, hand=hand, side=side): if t < .12: return blend('Clavicle.' + side, upper, t / .12) if t < .42: return {upper: 1} if t < .58: return blend(upper, fore_b, (t - .42) / .16) if t < .9: return {fore_b: 1} return blend(fore_b, hand, (t - .9) / .1) self.tube('Arm_' + side, [a, a.lerp(b, .45), b, b.lerp(c, .45), c], [.056 * lb * s, .055 * lb * s, .041 * lb * s, .047 * lb * s, .033 * lb * s], arm_kind, upper, 16, 30, weight_fn=arm_weights, pre=False) self.ell('Deltoid_' + side, a + V((sx * .012 * s, 0, .008 * s)), (.066 * lb * s, .066 * lb * s, .07 * lb * s), arm_kind, 'UpperArm.' + side, 16, 10, pre=False, weights=lambda v, side=side: blend('Clavicle.' + side, 'UpperArm.' + side, .6)) # Legs in pre-space (no bending below the waist). self.leg = {} for side, sx in SIDES: hp = V((sx * .092 * w, .005, H - .03)) if sp.get('digitigrade'): kn = V((sx * .10 * w, -.12, self.knee + .06)) an = V((sx * .105 * w, .10, .21)) toe = V((sx * .11 * w, -.07, .03)) else: kn = V((sx * .10 * w, -.035, self.knee)) an = V((sx * .105 * w, .012, self.ankle)) toe = V((sx * .11 * w, -.16, .025)) if self.hover: kn += V((0, -.04 if sx > 0 else .03, .04)) toe = an + V((0, -.05, -.1)) self.leg[side] = (hp, kn, an, toe) thigh, shin, foot = 'Thigh.' + side, 'Shin.' + side, 'Foot.' + side def leg_weights(t, thigh=thigh, shin=shin, foot=foot): if t < .08: return blend('Pelvis', thigh, t / .08) if t < .42: return {thigh: 1} if t < .6: return blend(thigh, shin, (t - .42) / .18) if t < .92: return {shin: 1} return blend(shin, foot, (t - .92) / .08) self.tube('Leg_' + side, [hp, hp.lerp(kn, .46), kn, kn.lerp(an, .45), an], [.088 * lb, .08 * lb, .056 * lb, .062 * lb, .04 * lb], leg_kind, thigh, 16, 30, weight_fn=leg_weights) if boot_kind: heel = an + V((0, .035, -.045 if not sp.get('digitigrade') else -.03)) self.tube('Boot_' + side, [heel, an.lerp(toe, .45) + V((0, 0, .012)), toe + V((0, -.012, .012))], [.05 * lb, .058 * lb, .03 * lb], boot_kind, foot, 12, 12, flatten=.72, smooth=True) self.ring('BootCuff_' + side, an + V((0, 0, .045)), (0, 0, 1), .052 * lb, .03, boot_kind, 'Shin.' + side, 14) # ------------------------------------------------------------ hands def fist(self, side, kind, grip_r): """Closed hand around the grip axis in final space (rigid on the hand bone).""" s, lb = self.s, self.sp['limb'] g, u, f = self.G[side], self.u[side], self.f[side] fore = (self.W[side] - self.elbow[side]).normalized() back = (fore - u * fore.dot(u)).normalized() across = u.cross(back) hand = 'Hand.' + side self.ell('Palm_' + side, g - back * (grip_r + .012 * s * lb) * .4, (.036 * s * lb, .048 * s * lb, .036 * s * lb), kind, hand, 14, 9, pre=False, axes=[across, u, back]) for k in range(4): off = u * ((k - 1.5) * .02 * s * lb) pts = [] for q in range(6): ang = -.5 + q * (3.5 / 5) pts.append(g + off + (back * cos(ang) + across * sin(ang)) * (grip_r + .011 * s * lb)) self.tube('Finger_%s%d' % (side, k), pts, [.012 * s * lb, .013 * s * lb, .011 * s * lb], kind, hand, 8, 10, pre=False) self.tube('Thumb_' + side, [g - back * grip_r * .8 - across * .03 * s * lb + u * .03 * s * lb, g - across * (grip_r + .012 * s * lb) + u * .03 * s * lb, g + back * grip_r * .4 - across * (grip_r + .004 * s) + u * .036 * s * lb], [.014 * s * lb, .013 * s * lb, .01 * s * lb], kind, hand, 8, 8, pre=False) def open_hand(self, side, kind, claws=None, spread=.12): """Open, slightly curled hand pointing along u with palm facing -f; optional claws.""" s, lb = self.s, self.sp['limb'] g, u, f = self.G[side], self.u[side], self.f[side] across = u.cross(f) hand = 'Hand.' + side self.ell('Palm_' + side, g, (.04 * s * lb, .05 * s * lb, .02 * s * lb), kind, hand, 14, 9, pre=False, axes=[across, u, f]) for k in range(4): base = g + u * .04 * s * lb + across * ((k - 1.5) * .022 * s * lb) dirn = (u + across * (k - 1.5) * spread).normalized() p1 = base + dirn * .035 * s * lb - f * .008 * s p2 = p1 + dirn * .03 * s * lb - f * .018 * s self.tube('Finger_%s%d' % (side, k), [base, p1, p2], [.012 * s * lb, .011 * s * lb, .009 * s * lb], kind, hand, 8, 8, pre=False) if claws: self.cone('Claw_%s%d' % (side, k), p2, p2 + dirn * .05 * s * lb - f * .03 * s, .01 * s * lb, claws, hand, 6, pre=False) inner = -1 if side == 'L' else 1 tb = g + across * .035 * s * lb * inner self.tube('Thumb_' + side, [g + across * .02 * s * lb * inner, tb + u * .03 * s * lb, tb + u * .055 * s * lb - f * .01 * s], [.014 * s * lb, .012 * s * lb, .01 * s * lb], kind, hand, 8, 8, pre=False) if claws: self.cone('ThumbClaw_' + side, tb + u * .055 * s * lb, tb + u * .09 * s * lb - f * .02 * s, .009 * s * lb, claws, hand, 6, pre=False) def fist_free(self, side, kind): s, lb = self.s, self.sp['limb'] g = self.G[side] self.ell('Fist_' + side, g, (.045 * s * lb, .05 * s * lb, .055 * s * lb), kind, 'Hand.' + side, 14, 9, pre=False) for k in range(4): self.ell('Knuckle_%s%d' % (side, k), g + V(((k - 1.5) * .02 * s * lb, -.04 * s * lb, .012 * s)), (.013 * s * lb, .014 * s * lb, .014 * s * lb), kind, 'Hand.' + side, 8, 6, pre=False) # ------------------------------------------------------------ head def face(self, kind='skin', eyes='dark', ears='human', nose=1.0, jaw=1.0, brow=1.0): hd, hc = self.sp['head'], self.head_c self.ell('Skull', (0, .0, hc), (.088 * hd, .1 * hd, .108 * hd), kind, 'Head', 20, 14) self.ell('Jaw', (0, -.042 * hd, hc - .058 * hd), (.064 * hd * jaw, .062 * hd, .05 * hd * jaw), kind, 'Head', 16, 10) self.ell('Brow', (0, -.078 * hd, hc + .026 * hd), (.068 * hd, .028 * hd * brow, .017 * hd * brow), kind, 'Head', 14, 8) self.tube('Nose', [(0, -.088 * hd, hc + .02 * hd), (0, -.106 * hd * nose, hc - .012 * hd), (0, -.1 * hd * nose, hc - .03 * hd)], [.012 * hd * nose, .018 * hd * nose, .014 * hd * nose], kind, 'Head', 10, 8) for side, sx in SIDES: self.ell('Eye_' + side, (sx * .032 * hd, -.084 * hd, hc + .006 * hd), (.014 * hd, .008 * hd, .011 * hd), eyes, 'Head', 10, 7) if ears == 'human': self.ell('Ear_' + side, (sx * .088 * hd, .0, hc - .002), (.012 * hd, .024 * hd, .034 * hd), kind, 'Head', 10, 7) elif ears == 'elf': self.tube('Ear_' + side, [(sx * .082 * hd, .01, hc - .02 * hd), (sx * .13 * hd, .04 * hd, hc + .03 * hd), (sx * .19 * hd, .08 * hd, hc + .1 * hd)], [.022 * hd, .016 * hd, .002], kind, 'Head', 8, 8, flatten=.4, ref=(0, 0, 1)) elif ears == 'orc': self.tube('Ear_' + side, [(sx * .085 * hd, .01, hc - .01 * hd), (sx * .13 * hd, .03 * hd, hc + .02 * hd), (sx * .17 * hd, .05 * hd, hc + .05 * hd)], [.026 * hd, .02 * hd, .002], kind, 'Head', 8, 8, flatten=.4, ref=(0, 0, 1)) elif ears == 'troll': self.tube('Ear_' + side, [(sx * .085 * hd, .0, hc), (sx * .15 * hd, .03 * hd, hc - .02 * hd), (sx * .22 * hd, .06 * hd, hc - .07 * hd)], [.032 * hd, .026 * hd, .003], kind, 'Head', 8, 8, flatten=.35, ref=(0, 0, 1)) # ------------------------------------------------------------ styles: grips, weapons def bind_grips(self): st, s, L = self.sp['style'], self.s, self.L shR, shL = self.sh['R'], self.sh['L'] self.G, self.u, self.f, self.pole = {}, {}, {}, {} self.target = {'R': 'Weapon', 'L': 'Offhand'} self.offhand_parent = 'Chest' self.weapon_parent = 'Chest' if st == 'blade_shield': self.G['R'] = shR + L * V((.03, -.50, -.60)) self.u['R'], self.f['R'], _ = frame((0, -.55, 1), (0, -1, 0)) self.G['L'] = shL + L * V((-.02, -.54, -.50)) self.u['L'], self.f['L'], _ = frame((0, 0, 1), (0, -1, 0)) elif st == 'bow': self.G['L'] = shL + L * V((-.12, -.84, -.12)) self.u['L'], self.f['L'], _ = frame((.1, 0, 1), (0, -1, 0)) self.G['R'] = self.G['L'] + V((0, .30 * L, .012 * s)) self.u['R'], self.f['R'], _ = frame((0, -1, 0), (-1, 0, 0)) self.weapon_parent = 'Offhand' elif st == 'staff': self.G['R'] = shR + L * V((-.14, -.30, -.60)) self.u['R'], self.f['R'], _ = frame((-.08, -.12, 1), (0, -1, 0)) self.G['L'] = shL + L * V((-.02, -.60, -.42)) self.u['L'], self.f['L'], _ = frame((0, -1, .35), (0, 0, -1)) elif st == 'club2h': self.G['R'] = shR + L * V((.30, -.55, -.60)) self.u['R'], self.f['R'], _ = frame((.25, -.5, 1), (0, -1, 0)) self.G['L'] = self.G['R'] + self.u['R'] * .17 * s self.u['L'], self.f['L'] = self.u['R'], self.f['R'] self.target['L'] = 'Weapon' elif st == 'axe': self.G['R'] = shR + L * V((-.12, -.42, -.64)) self.u['R'], self.f['R'], _ = frame((-.3, -.5, 1), (0, -1, 0)) self.G['L'] = shL + L * V((-.06, -.40, -.62)) self.u['L'], self.f['L'], _ = frame((0, 0, 1), (0, -1, 0)) elif st == 'claws': for side, sx in SIDES: self.G[side] = self.sh[side] + L * V((sx * -.06, -.58, -.46)) self.u[side], self.f[side], _ = frame((sx * -.1, -1, -.25), (0, 0, -1)) for side, sx in SIDES: self.pole[side] = self.sh[side] + L * V((sx * .8, .35, -.35)) if st == 'bow': self.pole['R'] = shR + L * V((-.8, .7, .2)) self.pole['L'] = shL + L * V((.7, .1, -.7)) def weapon_frame(self, side): g, u, f = self.G[side], self.u[side], self.f[side] side_v = u.cross(f) return lambda x, y, z: g + side_v * x * self.s + f * y * self.s + u * z * self.s def build_weapon(self): sp, st, s = self.sp, self.sp['style'], self.s if st == 'blade_shield': F = self.weapon_frame('R') u, f = self.u['R'], self.f['R'] if sp['weapon'] == 'sword': self.tube('Sword_Grip', [F(0, 0, -.07), F(0, 0, .07)], [.019 * s, .019 * s], 'leather', 'Weapon', 10, 4, groove=.12, lobes=8, pre=False) self.ell('Sword_Pommel', F(0, 0, -.09), (.03 * s, .03 * s, .026 * s), 'trim', 'Weapon', 12, 8, pre=False, axes=[u.cross(f), f, u]) self.plate('Sword_Guard', [F(-.12, 0, .075), F(-.02, 0, .09), F(.02, 0, .09), F(.12, 0, .075), F(.02, 0, .065), F(-.02, 0, .065)], F(0, 0, .077), .026 * s, 'trim', 'Weapon', normal=f, pre=False) blade = [F(-.028, 0, .09), F(.028, 0, .09), F(.024, 0, .62), F(0, 0, .72), F(-.024, 0, .62)] self.plate('Sword_Blade', blade, F(0, 0, .38), .016 * s, 'metal', 'Weapon', normal=f, pre=False) self.plate('Sword_Blade_Back', blade, F(0, 0, .38), .016 * s, 'metal', 'Weapon', normal=-f, pre=False) self.tube('Sword_Fuller', [F(0, -.008, .12), F(0, -.008, .5)], [.004 * s, .003 * s], 'dark', 'Weapon', 6, 4, pre=False) grip_r = .019 * s else: self.tube('Hammer_Haft', [F(0, 0, -.14), F(0, 0, .2), F(0, 0, .46)], [.022 * s, .021 * s, .024 * s], 'wood', 'Weapon', 10, 10, groove=.05, pre=False) for k, z in enumerate((-.03, .1, .38)): self.ring('Hammer_Band_%d' % k, F(0, 0, z), u, .026 * s, .018 * s, 'trim', 'Weapon', 12, pre=False) side_v = u.cross(f) self.tube('Hammer_Head', [F(-.13, 0, .5), F(0, 0, .5), F(.13, 0, .5)], [.058 * s, .066 * s, .058 * s], 'metal', 'Weapon', 8, 8, pre=False, ref=u, smooth=False) for q in (-1, 1): self.ring('Hammer_Face_%d' % q, F(q * .14, 0, .5), side_v, .062 * s, .02 * s, 'trim', 'Weapon', 8, pre=False) self.plate('Hammer_Rune', [F(-.03, -.07, .47), F(.03, -.07, .47), F(.03, -.07, .53), F(-.03, -.07, .53)], F(0, -.07, .5), .01 * s, 'glow', 'Weapon', normal=f, pre=False) grip_r = .022 * s self.fist('R', 'leather' if sp['tag'] == 'Mensch' else 'skin', grip_r) # Shield on the left arm. G = self.weapon_frame('L') fL = self.f['L'] self.tube('Shield_Handle', [G(0, 0, -.06), G(0, 0, .06)], [.018 * s, .018 * s], 'leather', 'Offhand', 8, 3, pre=False) self.fist('L', 'leather' if sp['tag'] == 'Mensch' else 'skin', .018 * s) if sp['shield'] == 'kite': outline = [(0, .28), (.19, .22), (.2, .03), (.11, -.2), (0, -.36), (-.11, -.2), (-.2, .03), (-.19, .22)] rim = [G(x * 1.07, .05, z * 1.06) for x, z in outline] face = [G(x, .068, z) for x, z in outline] self.plate('Shield_Rim', rim, G(0, .05, -.02), .03 * s, 'metal', 'Offhand', normal=fL, pre=False) self.plate('Shield_Face', face, G(0, .07, -.02), .03 * s, 'cloth', 'Offhand', normal=fL, pre=False) self.plate('Shield_Cross_V', [G(-.028, .1, .22), G(.028, .1, .22), G(.028, .1, -.28), G(-.028, .1, -.28)], G(0, .1, -.03), .012 * s, 'trim', 'Offhand', normal=fL, pre=False) self.plate('Shield_Cross_H', [G(-.15, .1, .07), G(.15, .1, .07), G(.15, .1, .02), G(-.15, .1, .02)], G(0, .1, .045), .012 * s, 'trim', 'Offhand', normal=fL, pre=False) else: outline = [(cos(2 * pi * k / 20) * .27, sin(2 * pi * k / 20) * .27) for k in range(20)] self.plate('Shield_Face', [G(x, .06, z) for x, z in outline], G(0, .07, 0), .045 * s, 'wood', 'Offhand', normal=fL, pre=False) self.ring('Shield_Rim', G(0, .055, 0), fL, .275 * s, .045 * s, 'metal', 'Offhand', 24, pre=False) self.ell('Shield_Boss', G(0, .1, 0), (.07 * s, .07 * s, .04 * s), 'trim', 'Offhand', 14, 8, pre=False, axes=[fL.cross(self.u['L']), self.u['L'], fL]) for k in range(8): a = 2 * pi * k / 8 self.ell('Shield_Rivet_%d' % k, G(cos(a) * .22, .1, sin(a) * .22), (.014 * s, .014 * s, .012 * s), 'trim', 'Offhand', 8, 5, pre=False) for k, (x0, z0, x1, z1) in enumerate([(-.1, .12, -.02, -.1), (.02, .12, .1, -.1), (-.06, .0, .06, .0)]): self.tube('Shield_Rune_%d' % k, [G(x0, .1, z0), G(x1, .1, z1)], [.009 * s, .009 * s], 'glow', 'Offhand', 6, 3, pre=False) elif st == 'bow': B = self.weapon_frame('L') fL = self.f['L'] pts = [B(0, -.07, -.62), B(0, .02, -.5), B(0, -.02, -.25), B(0, 0, 0), B(0, -.02, .25), B(0, .02, .5), B(0, -.07, .62)] self.tube('Bow_Limbs', pts, [.009 * s, .016 * s, .02 * s, .024 * s, .02 * s, .016 * s, .009 * s], 'wood', 'Offhand', 8, 34, flatten=.7, pre=False) self.tube('Bow_Grip', [B(0, 0, -.07), B(0, 0, .07)], [.026 * s, .026 * s], 'leather', 'Offhand', 10, 4, groove=.1, lobes=8, pre=False) for q in (-1, 1): self.cone('Bow_Tip_%d' % q, B(0, -.07, q * .6), B(0, -.11, q * .66), .012 * s, 'trim', 'Offhand', 6, pre=False) self.plate('Bow_Leaf_%d' % q, [B(-.035, .0, q * .16), B(0, .01, q * .1), B(.035, .0, q * .16), B(0, .01, q * .26)], B(0, .02, q * .17), .008 * s, 'trim', 'Offhand', normal=fL, pre=False) # String: tips on the bow, middle on the draw hand; the weights stretch it when drawn. top, bottom, nock = B(0, -.08, .6), B(0, -.08, -.6), self.G['R'].copy() vs, fs, ws = [], [], [] n = 24 for j in range(n + 1): t = j / n p = top.lerp(nock, t * 2) if t < .5 else nock.lerp(bottom, (t - .5) * 2) pull = 1 - abs(t - .5) * 2 for k in range(4): a = 2 * pi * k / 4 vs.append(p + V((cos(a), sin(a), 0)) * .003 * s) ws.append({'Offhand': 1 - pull, 'Weapon': pull} if pull < 1 else {'Weapon': 1}) if j: for k in range(4): a0, b0 = (j - 1) * 4 + k, (j - 1) * 4 + (k + 1) % 4 fs.append((a0, b0, b0 + 4, a0 + 4)) self.mesh('Bow_String', vs, fs, 'cloth2', 'Offhand', ws, True, None, False) # Draw hand pinching the nock, and the arrow on its own bone (hidden after release). self.fist('L', 'leather', .026 * s) self.open_hand('R', 'leather', spread=.04) aim = (self.G['L'] - self.G['R']).normalized() tip = self.G['R'] + aim * .7 * s self.tube('Arrow_Shaft', [self.G['R'] - aim * .02 * s, tip], [.006 * s, .006 * s], 'wood', 'Arrow', 6, 4, pre=False) self.cone('Arrow_Head', tip, tip + aim * .07 * s, .016 * s, 'trim', 'Arrow', 4, pre=False) for k in range(3): a = 2 * pi * k / 3 off = V((cos(a), 0, sin(a))) * .02 * s self.plate('Arrow_Fletch_%d' % k, [self.G['R'] + aim * .03 * s, self.G['R'] + aim * .03 * s + off, self.G['R'] + aim * .12 * s + off * .6, self.G['R'] + aim * .13 * s], self.G['R'] + aim * .08 * s + off * .5, .003 * s, 'cloth2', 'Arrow', normal=V((sin(a), 0, -cos(a))), pre=False) elif st == 'staff': F = self.weapon_frame('R') u = self.u['R'] self.tube('Staff_Shaft', [F(0, 0, -.5), F(0, .01, .1), F(0, 0, .55)], [.012 * s, .014 * s, .016 * s], 'wood', 'Weapon', 8, 16, groove=.06, pre=False) for k, z in enumerate((-.06, .08, .5)): self.ring('Staff_Band_%d' % k, F(0, 0, z), u, .018 * s, .014 * s, 'trim', 'Weapon', 10, pre=False) for q in (-1, 1): self.tube('Staff_Crescent_%d' % q, [F(0, 0, .55), F(q * .08, 0, .62), F(q * .085, 0, .74), F(q * .04, 0, .8)], [.012 * s, .011 * s, .008 * s, .003 * s], 'trim', 'Weapon', 8, 14, pre=False) self.ell('Staff_Orb', F(0, 0, .69), (.05 * s, .05 * s, .05 * s), 'glow', 'Weapon', 14, 10, pre=False) for q in (-1, 1): self.tube('Staff_Ribbon_%d' % q, [F(q * .02, 0, .5), F(q * .05, .02, .38), F(q * .04, .04, .26)], [.008 * s, .007 * s, .003 * s], 'cloth', 'Weapon', 6, 8, flatten=.25, pre=False) self.fist('R', 'skin', .014 * s) self.open_hand('L', 'skin', spread=.16) elif st == 'club2h': F = self.weapon_frame('R') u, f = self.u['R'], self.f['R'] side_v = u.cross(f) self.tube('Club_Haft', [F(0, 0, -.16), F(0, 0, .25), F(0, 0, .5)], [.034 * s, .036 * s, .05 * s], 'wood', 'Weapon', 10, 12, groove=.08, pre=False) self.ell('Club_Head', F(0, 0, .66), (.12 * s, .12 * s, .2 * s), 'stone', 'Weapon', 14, 10, pre=False, axes=[side_v, f, u], bumps=.12, smooth=False) for k in range(7): a = 2 * pi * k / 7 radial = side_v * cos(a) + f * sin(a) base = F(0, 0, .62 + .06 * (k % 2)) + radial * .1 * s self.shard('Club_Spike_%d' % k, [base, base + radial * .05 * s, base + radial * .09 * s + u * .02 * s], .03 * s, 'stone', 'Weapon', pre=False) for k, z in enumerate((.02, .44)): self.ring('Club_Wrap_%d' % k, F(0, 0, z), u, .042 * s, .05 * s, 'leather', 'Weapon', 12, pre=False) self.fist('R', 'skin', .036 * s) self.fist('L', 'skin', .036 * s) elif st == 'axe': F = self.weapon_frame('R') u, f = self.u['R'], self.f['R'] self.tube('Axe_Haft', [F(0, 0, -.2), F(0, 0, .2), F(0, 0, .58)], [.022 * s, .022 * s, .025 * s], 'wood', 'Weapon', 10, 10, groove=.05, pre=False) for k, z in enumerate((-.06, .08, .22)): self.ring('Axe_Wrap_%d' % k, F(0, 0, z), u, .026 * s, .03 * s, 'leather', 'Weapon', 10, pre=False) blade = [F(0, .02, .6), F(0, .16, .72), F(0, .24, .64), F(0, .25, .46), F(0, .17, .36), F(0, .02, .44)] self.plate('Axe_Blade', blade, F(0, .13, .54), .018 * s, 'metal', 'Weapon', normal=u.cross(f), pre=False) self.plate('Axe_Blade_Back', blade, F(0, .13, .54), .018 * s, 'metal', 'Weapon', normal=-u.cross(f), pre=False) self.tube('Axe_Edge', [F(0, .16, .72), F(0, .235, .66), F(0, .25, .55), F(0, .24, .46), F(0, .17, .36)], [.006 * s] * 5, 'bone', 'Weapon', 6, 14, pre=False) self.shard('Axe_Spike', [F(0, -.02, .53), F(0, -.1, .52), F(0, -.18, .55)], .03 * s, 'metal', 'Weapon', pre=False) self.ring('Axe_Collar', F(0, 0, .53), u, .03 * s, .1 * s, 'metal', 'Weapon', 10, pre=False) self.fist('R', 'skin', .022 * s) self.fist_free('L', 'skin') elif st == 'claws': for side, _ in SIDES: self.open_hand(side, 'skin', claws='bone', spread=.18) # ------------------------------------------------------------ shared gear pieces def build(self): getattr(self, 'gear_' + self.key[2:])() self.build_weapon() def armour_loft(self, name, z0, z1, grow, kind, bone_fn=None, sides=24, steps=12, bulge=.07): secs = [(z, rx + grow, ry + grow) for z, rx, ry in self.torso_sections if z0 - .001 <= z <= z1 + .001] return self.loft(name, secs, kind, bone_fn or self.torso_weights, sides, steps, front_bulge=bulge) def section_at(self, z): secs = self.torso_sections for a, b in zip(secs, secs[1:]): if a[0] <= z <= b[0]: t = (z - a[0]) / (b[0] - a[0]) return a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t return secs[0][1], secs[0][2] def pauldron(self, side, sx, kind, trim, layers=3, size=1.0, spikes=0, spike_kind='bone'): s = self.s a = self.sh[side] out = V((sx, 0, 0)) for k in range(layers): c = a + out * (.03 + .018 * k) * s * size + V((0, 0, (.035 - .035 * k) * s * size)) self.ell('Pauldron_%s%d' % (side, k), c, ((.085 - .008 * k) * s * size, (.09 - .006 * k) * s * size, (.058 - .006 * k) * s * size), kind, 'UpperArm.' + side, 16, 9, smooth=False, pre=False) self.ring('Pauldron_Trim_' + side, a + out * .03 * s * size, (sx * .4, 0, 1), .087 * s * size, .014 * s, trim, 'UpperArm.' + side, 18, pre=False) for k in range(spikes): base = a + out * (.05 * s * size) + V((0, (k - (spikes - 1) / 2) * .045 * s * size, .07 * s * size)) self.shard('Pauldron_Spike_%s%d' % (side, k), [base, base + V((sx * .05, 0, .09)) * s * size, base + V((sx * .08, 0, .16)) * s * size], .022 * s * size, spike_kind, 'UpperArm.' + side, pre=False) def bracer(self, side, kind, trim=None, frac=(.55, .9), grow=.012): a, b = self.elbow[side], self.W[side] p0, p1 = a.lerp(b, frac[0]), a.lerp(b, frac[1]) lb, s = self.sp['limb'], self.s self.tube('Bracer_' + side, [p0, p0.lerp(p1, .5), p1], [(.046 + grow) * lb * s, (.047 + grow) * lb * s, (.037 + grow) * lb * s], kind, 'Forearm.' + side, 14, 6, smooth=False, pre=False) if trim: self.ring('Bracer_Trim_' + side, p1, b - a, (.04 + grow) * lb * s, .012 * s, trim, 'Forearm.' + side, 14, pre=False) def belt(self, kind, buckle, z=None, grow=.012): z = z if z is not None else self.waist - .02 rx, ry = self.section_at(z) self.loft('Belt', [(z - .025, rx + grow, ry + grow), (z + .025, rx + grow, ry + grow)], kind, self.torso_weights, 24, 2) y = -ry - grow - .004 self.plate('Buckle', [(-.03, y, z + .028), (.03, y, z + .028), (.03, y, z - .028), (-.03, y, z - .028)], (0, y, z), .012, buckle, 'Pelvis') return rx + grow, ry + grow def hanging(self, name, outline, center, depth, kind, normal, top, bottom, share=.5): return self.plate(name, outline, center, depth, kind, 'Pelvis', normal=normal, weights=lambda v: self.skirt_weights(top, bottom, share)(v[2], v[0])) # ------------------------------------------------------------ people def gear_mensch(self): self.build_body('cloth', 'metal', 'metal', 'metal', chest=1.04) H, w = self.hip, self.sp['w'] self.armour_loft('Breastplate', H + .13, H + .55, .016, 'metal', bulge=.1) rx, ry = self.section_at(H + .13) self.loft('Breastplate_Rim', [(H + .12, rx + .022, ry + .022), (H + .145, rx + .022, ry + .022)], 'trim', self.torso_weights, 24, 2) self.ring('Gorget', (0, 0, self.neck - .01), (0, 0, 1), .082, .05, 'metal', 'Neck', 18) self.ring('Gorget_Trim', (0, 0, self.neck + .018), (0, 0, 1), .078, .012, 'trim', 'Neck', 18) # Chest emblem: a gold sun on the breastplate. rx, ry = self.section_at(H + .40) ey = -(ry * 1.1 + .016) - .004 self.ell('Emblem', (0, ey, H + .40), (.035, .012, .035), 'trim', 'Chest', 12, 6, smooth=False) for k in range(8): a = 2 * pi * k / 8 self.shard('Emblem_Ray_%d' % k, [(cos(a) * .035, ey + .002, H + .40 + sin(a) * .035), (cos(a) * .07, ey + .008, H + .40 + sin(a) * .07)], .012, 'trim', 'Chest') # Faulds and tabard. for k, z in enumerate((H + .08, H + .02, H - .04)): rx, ry = self.section_at(max(H - .07, z)) g = .02 + .008 * k self.loft('Fauld_%d' % k, [(z - .035, rx + g + .006, ry + g + .006), (z + .03, rx + g, ry + g)], 'metal', self.skirt_weights(H + .1, H - .08, .2), 24, 2, smooth=False) rx, ry = self.belt('leather', 'trim', H + .115) for q, y in ((-1, -ry - .03), (1, ry + .03)): out = [(-.12, y, H + .1), (.12, y, H + .1), (.11, y, H - .3), (0, y, H - .36), (-.11, y, H - .3)] self.hanging('Tabard_%d' % q, out, (0, y, H - .1), .014, 'cloth', (0, q, 0), H + .1, H - .36) trim = [(-.115, y + q * .005, H - .22), (.115, y + q * .005, H - .22), (.105, y + q * .005, H - .29), (0, y + q * .005, H - .345), (-.105, y + q * .005, H - .29)] self.hanging('Tabard_Trim_%d' % q, trim, (0, y + q * .005, H - .28), .008, 'trim', (0, q, 0), H + .1, H - .36) for side, sx in SIDES: self.pauldron(side, sx, 'metal', 'trim', 3) self.ell('Couter_' + side, self.elbow[side], (.052 * self.s, .052 * self.s, .052 * self.s), 'metal', 'Forearm.' + side, 14, 9, pre=False, smooth=False) self.bracer(side, 'metal', 'trim', (.5, .92), .006) hp, kn, an, toe = self.leg[side] self.ell('Poleyn_' + side, kn + V((0, -.03, 0)), (.058, .045, .058), 'metal', 'Shin.' + side, 14, 9, smooth=False, weights=lambda v, side=side: blend('Thigh.' + side, 'Shin.' + side, .6)) self.ring('Poleyn_Trim_' + side, kn + V((0, -.02, .0)), (0, 1, .2), .052, .012, 'trim', 'Shin.' + side, 14) # Great helm with visor slit, gold cross and a white plume. hd, hc = self.sp['head'], self.head_c self.ell('Helm', (0, -.004, hc + .004), (.1 * hd, .112 * hd, .122 * hd), 'metal', 'Head', 20, 14) self.ring('Helm_Crown', (0, -.004, hc + .06 * hd), (0, 0, 1), .096 * hd, .018, 'trim', 'Head', 20) self.plate('Visor_Slit', [(-.07, -.108 * hd, hc + .018), (.07, -.108 * hd, hc + .018), (.07, -.104 * hd, hc + .002), (-.07, -.104 * hd, hc + .002)], (0, -.11 * hd, hc + .01), .008, 'dark', 'Head') self.plate('Helm_Cross_V', [(-.012, -.115 * hd, hc + .1), (.012, -.115 * hd, hc + .1), (.012, -.112 * hd, hc - .08), (-.012, -.112 * hd, hc - .08)], (0, -.118 * hd, hc + .01), .008, 'trim', 'Head') for k in range(4): self.ell('Breath_%d' % k, ((k - 1.5) * .018, -.106 * hd, hc - .045), (.005, .004, .005), 'dark', 'Head', 6, 4) self.tube('Plume', [(0, .0, hc + .12), (0, .06, hc + .2), (0, .16, hc + .2), (0, .24, hc + .08), (0, .26, hc - .04)], [.022, .04, .045, .03, .006], 'cloth2', 'Head', 12, 20, groove=.25, lobes=7) def gear_elf(self): self.build_body('leather', 'cloth', 'cloth2', 'leather', chest=.96) H, hd, hc = self.hip, self.sp['head'], self.head_c self.armour_loft('Jerkin', H + .1, H + .5, .008, 'cloth', bulge=.08) # Leaf plates over chest and shoulders. for k, (x, z, sc) in enumerate([(-.07, H + .42, 1), (.07, H + .42, 1), (-.05, H + .3, .8), (.05, H + .3, .8), (0, H + .2, .7)]): rx, ry = self.section_at(z) y = -ry * 1.05 - .012 self.plate('Leaf_Plate_%d' % k, [(x, y, z + .06 * sc), (x + .045 * sc, y, z + .01 * sc), (x, y, z - .06 * sc), (x - .045 * sc, y, z + .01 * sc)], (x, y - .01, z), .014, 'cloth2', 'Chest' if z > self.chest else 'Spine') self.tube('Leaf_Vein_%d' % k, [(x, y - .012, z + .05 * sc), (x, y - .012, z - .05 * sc)], [.004, .003], 'trim', 'Chest' if z > self.chest else 'Spine', 6, 3) rx, ry = self.belt('leather', 'trim', H + .1) for q, y in ((-1, -ry - .02), (1, ry + .02)): for k, x in enumerate((-.08, .0, .08)): out = [(x - .045, y, H + .08), (x + .045, y, H + .08), (x + .035, y, H - .2), (x, y, H - .25), (x - .035, y, H - .2)] self.hanging('Tunic_%d_%d' % (q, k), out, (x, y, H - .06), .01, 'cloth', (0, q, 0), H + .08, H - .25, .55) lb = self.sp['limb'] for side, sx in SIDES: hp, kn, an, toe = self.leg[side] self.tube('Boot_Shaft_' + side, [an + V((0, 0, .02)), an.lerp(kn, .5), kn + V((0, -.01, -.03))], [.04 * lb + .012, .062 * lb + .012, .058 * lb + .012], 'leather', 'Shin.' + side, 14, 10) self.plate('Boot_Cuff_' + side, [kn + V((-.05, -.045, .0)), kn + V((.05, -.045, 0)), kn + V((0, -.06, .06))], kn + V((0, -.06, .01)), .012, 'trim', 'Shin.' + side) self.bracer(side, 'leather', 'trim', (.45, .92), .004) self.pauldron(side, sx, 'leather', 'trim', 2, .8) # Cloak on its own bone, hood gathered at the neck. cloak = [(0, .12, self.shoulder), (0, .18, self.waist), (0, .23, self.knee + .05)] self.tube('Cloak', cloak, [.2, .23, .27], 'cloth2', 'Chest', 18, 16, flatten=.18, ref=(1, 0, 0), weight_fn=lambda t: blend('Chest', 'Cloak', t * 1.2)) self.tube('Hood', [(-.11, .02, self.neck - .02), (0, .1, self.neck + .02), (.11, .02, self.neck - .02)], [.05, .065, .05], 'cloth2', 'Chest', 12, 12) self.ring('Cloak_Clasp', (0, -.1, self.neck - .03), (0, 1, 0), .02, .01, 'trim', 'Chest', 10) self.face('skin', 'dark', 'elf', nose=.9, jaw=.88, brow=.8) self.ell('Hair_Cap', (0, .03 * hd, hc + .035 * hd), (.093 * hd, .092 * hd, .09 * hd), 'hair', 'Head', 18, 12) for side, sx in SIDES: self.tube('Hair_Strand_' + side, [(sx * .05 * hd, -.07 * hd, hc + .08 * hd), (sx * .085 * hd, -.05 * hd, hc + .02 * hd), (sx * .09 * hd, -.02 * hd, hc - .08 * hd)], [.018 * hd, .016 * hd, .006 * hd], 'hair', 'Head', 10, 10, groove=.2, lobes=4) self.tube('Hair_Tail', [(0, .08 * hd, hc + .02), (0, .12 * hd, hc - .1), (0, .14, self.shoulder - .05), (0, .15, self.chest - .02)], [.04, .036, .028, .012], 'hair', 'Head', 12, 16, groove=.2, lobes=6, weight_fn=lambda t: {'Head': 1} if t < .3 else blend('Head', 'Chest', (t - .3) / .4)) self.ring('Circlet', (0, -.005, hc + .05 * hd), (0, .15, 1), .094 * hd, .01, 'trim', 'Head', 20) self.ell('Circlet_Gem', (0, -.1 * hd, hc + .055 * hd), (.012, .008, .014), 'glow', 'Head', 10, 6) # Quiver with arrows across the back. q0, q1 = V((.1, .17, self.shoulder + .06)), V((-.08, .16, self.waist + .02)) up = (q0 - q1).normalized() self.tube('Quiver', [q0, q1], [.045, .04], 'leather', 'Chest', 12, 4) self.ring('Quiver_Trim', q0.lerp(q1, .08), up, .048, .02, 'trim', 'Chest', 12) for k in range(4): off = V(((k - 1.5) * .018, .0, 0)) self.tube('Quiver_Arrow_%d' % k, [q0 + off, q0 + off + up * .1], [.005, .005], 'wood', 'Chest', 6, 3) self.plate('Quiver_Fletch_%d' % k, [q0 + off + up * .05, q0 + off + up * .1 + V((0, -.012, 0)), q0 + off + up * .12, q0 + off + up * .07 + V((0, .012, 0))], q0 + off + up * .085, .004, 'cloth', 'Chest', normal=(1, 0, 0)) self.extra_bones.append(('Cloak', V((0, .12, self.shoulder)), V((0, .2, self.waist)), 'Chest')) def gear_zwerg(self): self.build_body('cloth', 'cloth', 'cloth', 'leather', chest=1.05, belly=1.12) H, hd, hc = self.hip, self.sp['head'], self.head_c self.armour_loft('Cuirass', H + .1, H + .55, .02, 'metal', bulge=.12) for k in range(3): z = H + .16 + .1 * k rx, ry = self.section_at(z) self.loft('Cuirass_Band_%d' % k, [(z - .012, rx + .03, ry * 1.05 + .03), (z + .012, rx + .03, ry * 1.05 + .03)], 'trim', self.torso_weights, 24, 2) rx, ry = self.belt('leather', 'trim', H + .08, .03) self.plate('Belt_Plate', [(-.06, -ry - .012, H + .12), (.06, -ry - .012, H + .12), (.07, -ry - .012, H + .04), (-.07, -ry - .012, H + .04)], (0, -ry - .03, H + .08), .025, 'trim', 'Pelvis') w, d = self.sp['w'], self.sp['d'] self.loft('Mail_Skirt', [(H + .06, .17 * w + .02, .13 * d + .03), (H - .1, .19 * w + .03, .14 * d + .03), (H - .24, .2 * w + .035, .15 * d + .035)], 'metal', self.skirt_weights(H + .06, H - .24, .5), 24, 8, scallop=.04) for side, sx in SIDES: self.pauldron(side, sx, 'metal', 'trim', 3, 1.25) self.bracer(side, 'metal', 'trim', (.35, .92), .012) hp, kn, an, toe = self.leg[side] self.plate('Greave_' + side, [kn + V((-.05, -.05, -.02)), kn + V((.05, -.05, -.02)), an + V((.045, -.05, .05)), an + V((-.045, -.05, .05))], kn.lerp(an, .5) + V((0, -.08, 0)), .03, 'metal', 'Shin.' + side) self.ell('Knee_' + side, kn + V((0, -.045, 0)), (.058, .04, .055), 'trim', 'Shin.' + side, 12, 8, smooth=False) self.face('skin', 'dark', 'human', nose=1.35, jaw=1.05, brow=1.4) # Beard, braids and moustache. self.ell('Beard', (0, -.062 * hd, hc - .09 * hd), (.09 * hd, .07 * hd, .1 * hd), 'hair', 'Head', 18, 12, bumps=.06) for side, sx in SIDES: self.tube('Moustache_' + side, [(0, -.1 * hd, hc - .032 * hd), (sx * .04 * hd, -.1 * hd, hc - .045 * hd), (sx * .07 * hd, -.085 * hd, hc - .085 * hd)], [.016 * hd, .018 * hd, .008 * hd], 'hair', 'Head', 10, 10) top = V((sx * .035 * hd, -.09 * hd, hc - .14 * hd)) self.tube('Braid_' + side, [top, top + V((0, -.02, -.07)), top + V((sx * .005, -.022, -.15))], [.022, .02, .012], 'hair', 'Head', 10, 12, groove=.25, lobes=3, weight_fn=lambda t: blend('Head', 'Chest', t)) self.ring('Braid_Bead_' + side, top + V((sx * .004, -.022, -.12)), (0, 0, 1), .02, .025, 'trim', 'Chest', 10) # Dome helmet with nose guard and short horns. self.ell('Helm', (0, .004, hc + .03 * hd), (.1 * hd, .11 * hd, .094 * hd), 'metal', 'Head', 20, 12) self.ring('Helm_Rim', (0, .0, hc + .012 * hd), (0, 0, 1), .1 * hd, .03, 'trim', 'Head', 20) self.plate('Nose_Guard', [(-.014, -.112 * hd, hc + .05 * hd), (.014, -.112 * hd, hc + .05 * hd), (.01, -.118 * hd, hc - .03 * hd), (-.01, -.118 * hd, hc - .03 * hd)], (0, -.122 * hd, hc + .01), .01, 'metal', 'Head') for side, sx in SIDES: self.tube('Horn_' + side, [(sx * .08 * hd, 0, hc + .05 * hd), (sx * .14 * hd, -.01, hc + .08 * hd), (sx * .17 * hd, -.03, hc + .15 * hd)], [.025 * hd, .018 * hd, .002], 'bone', 'Head', 10, 10) def gear_troll(self): self.build_body('skin', 'skin', 'skin', None, chest=1.08, belly=1.2) H, hd, hc = self.hip, self.sp['head'], self.head_c rx, ry = self.belt('leather', 'bone', H + .06, .02) for q, y in ((-1, -ry - .02), (1, ry + .02)): out = [(-.14, y, H + .05), (.14, y, H + .05), (.12, y, H - .2), (.02, y, H - .3), (-.1, y, H - .24)] self.hanging('Loincloth_%d' % q, out, (0, y, H - .08), .016, 'leather', (0, q, 0), H + .05, H - .3) # Stone growths along shoulders, back and forearms. growths = [((.12, .08, self.shoulder + .02), (.2, .14, self.shoulder + .14)), ((-.12, .08, self.shoulder + .02), (-.22, .12, self.shoulder + .12)), ((0, .13, self.chest + .05), (0, .26, self.chest + .12)), ((.06, .13, self.chest - .08), (.1, .25, self.chest - .02)), ((-.07, .12, self.chest - .06), (-.12, .23, self.chest + .02)), ((0, .13, self.waist + .05), (0, .22, self.waist + .1))] for k, (a, b) in enumerate(growths): self.shard('Stone_Growth_%d' % k, [a, V(a).lerp(V(b), .55), b], .05, 'stone', 'Chest' if a[2] > self.chest - .1 else 'Spine', sides=5) for side, sx in SIDES: a, b = self.elbow[side], self.W[side] for k in range(2): p = a.lerp(b, .3 + .3 * k) out = (p - self.sh[side]).cross(V((0, 0, 1))).normalized() * (-sx) self.shard('Arm_Stone_%s%d' % (side, k), [p, p + out * .06 * self.s + V((0, .04, .04)) * self.s], .035 * self.s, 'stone', 'Forearm.' + side, pre=False, sides=5) hp, kn, an, toe = self.leg[side] for k, dx in enumerate((-.03, 0, .03)): self.ell('Toe_%s%d' % (side, k), toe + V((dx * 1.3, -.01, .01)), (.022, .03, .02), 'skin', 'Foot.' + side, 10, 7) self.tube('Foot_' + side, [an + V((0, .04, -.05)), an.lerp(toe, .5), toe], [.06, .07, .05], 'skin', 'Foot.' + side, 12, 10, flatten=.65) self.ring('Ankle_Wrap_' + side, an + V((0, 0, .04)), (0, 0, 1), .06, .04, 'leather', 'Shin.' + side, 12) self.face('skin', 'eye', 'troll', nose=1.5, jaw=1.35, brow=1.7) for side, sx in SIDES: self.cone('Tusk_' + side, (sx * .04 * hd, -.09 * hd, hc - .075 * hd), (sx * .055 * hd, -.12 * hd, hc - .0 * hd), .014 * hd, 'bone', 'Head', 8) self.tube('Hair_Tuft', [(0, -.02, hc + .1 * hd), (0, .05, hc + .13 * hd), (0, .12, hc + .08 * hd)], [.02, .03, .008], 'hair', 'Head', 8, 8, groove=.3) self.ring('Necklace', (0, 0, self.neck - .03), (0, -.3, 1), .1, .015, 'leather', 'Chest', 14) for k in range(5): a = pi * (.25 + .5 * k / 4) self.cone('Necklace_Tooth_%d' % k, (cos(a) * .1, -sin(a) * .1, self.neck - .05), (cos(a) * .1, -sin(a) * .11, self.neck - .1), .012, 'bone', 'Chest', 6) def gear_fee(self): self.build_body('cloth2', 'skin', 'skin', 'cloth2', chest=.92) H, hd, hc = self.hip, self.sp['head'], self.head_c w, d = self.sp['w'], self.sp['d'] self.loft('Dress', [(self.chest + .06, .18 * w, .115 * d), (self.waist, .15 * w, .11 * d), (H, .19 * w, .15 * d), (H - .18, .25 * w, .21 * d), (H - .32, .3 * w, .26 * d)], 'cloth', self.skirt_weights(H + .02, H - .32, .45), 28, 16, scallop=.08) self.loft('Bodice_Trim', [(self.chest + .05, .182 * w + .006, .118 * d + .006), (self.chest + .07, .18 * w + .006, .116 * d + .006)], 'trim', self.torso_weights, 24, 2) self.loft('Sash', [(self.waist - .02, .15 * w + .01, .11 * d + .01), (self.waist + .02, .15 * w + .01, .11 * d + .01)], 'trim', self.torso_weights, 24, 2) for side, sx in SIDES: self.ring('Bracelet_' + side, self.elbow[side].lerp(self.W[side], .85), self.W[side] - self.elbow[side], .036 * self.s * .74 + .004, .01, 'trim', 'Forearm.' + side, 12, pre=False) self.ell('Puff_Sleeve_' + side, self.sh[side] + V((sx * .02, 0, -.01)) * self.s, (.07 * self.s, .07 * self.s, .06 * self.s), 'cloth', 'UpperArm.' + side, 14, 9, pre=False, bumps=.08) self.face('skin', 'dark', 'elf', nose=.7, jaw=.85, brow=.6) self.ell('Hair', (0, .018, hc + .02 * hd), (.098 * hd, .108 * hd, .104 * hd), 'hair', 'Head', 18, 12, bumps=.05) self.ell('Hair_Bun', (0, .06 * hd, hc + .1 * hd), (.05 * hd, .05 * hd, .045 * hd), 'hair', 'Head', 14, 9, bumps=.1) for side, sx in SIDES: self.tube('Hair_Lock_' + side, [(sx * .075 * hd, -.05 * hd, hc + .0), (sx * .085 * hd, -.05 * hd, hc - .08 * hd), (sx * .08 * hd, -.03 * hd, hc - .15 * hd)], [.02, .018, .006], 'hair', 'Head', 10, 10, groove=.2, lobes=4) for k in range(6): a = pi * (-.1 + 1.2 * k / 5) c = (cos(a) * .09 * hd, -sin(a) * .09 * hd, hc + .07 * hd) self.ell('Flower_%d' % k, c, (.02, .02, .012), 'cloth2', 'Head', 8, 5, bumps=.3) self.ell('Flower_Heart_%d' % k, (c[0], c[1] - .005, c[2] + .008), (.007, .007, .006), 'glow', 'Head', 6, 4) # Two pairs of luminous wings with a vein frame. for side, sx in SIDES: root = V((sx * .04, .1, self.chest + .04)) for k, (span, rise, width) in enumerate(((.46, .2, .2), (.32, -.14, .13))): tip = root + V((sx * span, .08, rise)) mid = root.lerp(tip, .55) perp = V((0, 0, 1)) if k == 0 else V((0, 0, -1)) outline = [root, mid + perp * width * .9 + V((0, .02, 0)), tip + perp * width * .3, tip, tip - perp * width * .25, mid - perp * width * .35] self.plate('Wing_%s%d' % (side, k), outline, mid + V((0, .012, 0)), .01, 'glow', 'Wing.' + side, normal=(0, 1, 0)) self.tube('Wing_Vein_%s%d' % (side, k), [root, mid + perp * width * .3, tip], [.008, .006, .002], 'trim', 'Wing.' + side, 6, 10) self.extra_bones.append(('Wing.' + side, V((sx * .04, .1, self.chest + .04)), V((sx * .3, .16, self.chest + .1)), 'Chest')) def gear_ork(self): self.build_body('skin', 'skin', 'leather', 'leather', chest=1.1, belly=1.05) H, hd, hc = self.hip, self.sp['head'], self.head_c rx, ry = self.belt('leather', 'metal', H + .07, .02) for k in range(8): a = 2 * pi * (k + .5) / 8 x, y = cos(a) * (rx + .012), sin(a) * (ry + .012) n = V((cos(a), sin(a), 0)) t = V((-sin(a), cos(a), 0)) * .032 out = [V((x, y, H + .06)) + t, V((x, y, H + .06)) - t, V((x, y, H - .22)) - t * .8, V((x, y, H - .26)), V((x, y, H - .22)) + t * .8] self.hanging('Kilt_%d' % k, out, V((x, y, H - .08)), .014, 'leather' if k % 2 else 'trim', n, H + .06, H - .26) # Harness straps crossing the chest. for q in (-1, 1): pts = [] for z, x in ((self.shoulder, q * .15), (self.chest, q * .02), (self.waist + .03, -q * .12)): rx2, ry2 = self.section_at(z) pts.append((x, -ry2 * 1.08 - .008, z)) self.tube('Strap_%d' % q, pts, [.018, .018, .018], 'leather', 'Chest', 8, 12, flatten=.4, weight_fn=lambda t: self.torso_weights(self.shoulder - t * (self.shoulder - self.waist))) rx2, ry2 = self.section_at(self.chest) self.ring('Strap_Ring', (0, -ry2 * 1.08 - .012, self.chest), (0, 1, 0), .03, .012, 'metal', 'Chest', 12) self.pauldron('L', 1, 'metal', 'trim', 3, 1.3, spikes=3, spike_kind='metal') self.ell('Fur_Mantle', self.sh['R'] + V((.0, .03, .03)) * self.s, (.11 * self.s, .1 * self.s, .07 * self.s), 'fur', 'UpperArm.R', 16, 9, pre=False, bumps=.2) # Skull trophy on the belt. sk = V((-.1, -ry - .03, H + .02)) self.ell('Trophy_Skull', sk, (.04, .045, .045), 'bone', 'Pelvis', 12, 8) for sx in (-1, 1): self.ell('Trophy_Eye_%d' % sx, sk + V((sx * .016, -.036, .01)), (.011, .006, .01), 'dark', 'Pelvis', 8, 5) for side, sx in SIDES: self.bracer(side, 'metal', 'leather', (.45, .9), .01) p = self.elbow[side].lerp(self.W[side], .65) out = (p - self.sh[side]).cross(V((0, 0, 1))).normalized() * (-sx) for k in range(2): q = self.elbow[side].lerp(self.W[side], .55 + .2 * k) self.shard('Bracer_Spike_%s%d' % (side, k), [q + out * .05 * self.s, q + out * .1 * self.s], .015 * self.s, 'metal', 'Forearm.' + side, pre=False) hp, kn, an, toe = self.leg[side] self.ell('Toe_Cap_' + side, toe + V((0, .03, .015)), (.05, .06, .035), 'metal', 'Foot.' + side, 12, 8, smooth=False) self.face('skin', 'dark', 'orc', nose=1.2, jaw=1.3, brow=1.5) for side, sx in SIDES: self.cone('Tusk_' + side, (sx * .035 * hd, -.09 * hd, hc - .07 * hd), (sx * .045 * hd, -.11 * hd, hc - .02 * hd), .012 * hd, 'bone', 'Head', 8) self.plate('War_Paint_' + side, [(sx * .02, -.094 * hd, hc + .03), (sx * .06, -.08 * hd, hc + .03), (sx * .055, -.082 * hd, hc - .005), (sx * .02, -.096 * hd, hc)], (sx * .04, -.095 * hd, hc + .015), .004, 'trim', 'Head') for k in range(6): z = hc + .1 * hd - abs(k - 2.5) * .006 self.shard('Mohawk_%d' % k, [(0, -.06 * hd + k * .03 * hd, z), (0, -.06 * hd + k * .03 * hd + .02, z + .08 - abs(k - 2.5) * .01)], .024, 'hair', 'Head', sides=4) def gear_biest(self): self.build_body('fur', 'fur', 'fur', None, chest=1.08) H, hd, hc = self.hip, self.sp['head'], self.head_c self.armour_loft('Chest_Fur', self.chest - .1, self.chest + .12, .004, 'fur2', bulge=.12) rx, ry = self.belt('leather', 'metal', H + .07, .016) for q, y in ((-1, -ry - .02), (1, ry + .02)): out = [(-.1, y, H + .06), (.1, y, H + .06), (.08, y, H - .18), (0, y, H - .24), (-.08, y, H - .18)] self.hanging('Loincloth_%d' % q, out, (0, y, H - .06), .014, 'cloth', (0, q, 0), H + .06, H - .24) # Mane tufts around neck and shoulders. for k in range(14): a = 2 * pi * k / 14 base = V((cos(a) * .12, sin(a) * .1 + .02, self.neck - .02)) tip = base + V((cos(a) * .07, sin(a) * .07 + .03, -.05 + .03 * sin(3 * a))) self.shard('Mane_%d' % k, [base, base.lerp(tip, .5) + V((0, 0, .02)), tip], .04, 'fur2' if k % 3 == 0 else 'fur', 'Chest', sides=5) # Bone necklace. self.ring('Necklace', (0, -.01, self.neck - .06), (0, -.4, 1), .12, .01, 'leather', 'Chest', 14) for k in range(5): a = pi * (.2 + .6 * k / 4) self.cone('Necklace_Fang_%d' % k, (cos(a) * .12, -sin(a) * .115, self.neck - .075), (cos(a) * .12, -sin(a) * .125, self.neck - .13), .01, 'bone', 'Chest', 6) for side, sx in SIDES: self.bracer(side, 'leather', 'metal', (.5, .9), .012) hp, kn, an, toe = self.leg[side] self.tube('Paw_' + side, [an + V((0, .02, -.02)), an.lerp(toe, .6), toe], [.045, .052, .04], 'fur', 'Foot.' + side, 12, 10, flatten=.7) for k, dx in enumerate((-.025, 0, .025)): self.cone('Foot_Claw_%s%d' % (side, k), toe + V((dx, -.02, .0)), toe + V((dx * 1.3, -.07, -.02)), .01, 'bone', 'Foot.' + side, 6) # Beast head: long snout, ram horns, animal ears, amber eyes. self.ell('Skull', (0, .0, hc), (.085 * hd, .1 * hd, .1 * hd), 'fur', 'Head', 20, 14) self.ell('Snout', (0, -.1 * hd, hc - .04 * hd), (.05 * hd, .09 * hd, .045 * hd), 'fur', 'Head', 16, 10, axes=[(1, 0, 0), (0, 1, .3), (0, -.3, 1)]) self.ell('Muzzle_Light', (0, -.12 * hd, hc - .065 * hd), (.042 * hd, .07 * hd, .03 * hd), 'fur2', 'Head', 14, 8) self.ell('Nose', (0, -.18 * hd, hc - .02 * hd), (.022 * hd, .014 * hd, .015 * hd), 'dark', 'Head', 10, 6) self.ell('Brow', (0, -.07 * hd, hc + .03 * hd), (.07 * hd, .03 * hd, .02 * hd), 'fur', 'Head', 14, 8) for side, sx in SIDES: self.ell('Eye_' + side, (sx * .038 * hd, -.08 * hd, hc + .012 * hd), (.014 * hd, .008 * hd, .01 * hd), 'eye', 'Head', 10, 7) self.cone('Fang_' + side, (sx * .025 * hd, -.16 * hd, hc - .07 * hd), (sx * .025 * hd, -.16 * hd, hc - .1 * hd), .007 * hd, 'bone', 'Head', 6) self.tube('Ear_' + side, [(sx * .07 * hd, .02, hc + .04 * hd), (sx * .12 * hd, .04, hc + .06 * hd), (sx * .16 * hd, .06, hc + .05 * hd)], [.025, .02, .002], 'fur', 'Head', 8, 8, flatten=.4, ref=(0, 0, 1)) pts = [V((sx * .06 * hd, .02, hc + .07 * hd))] for q in range(1, 9): a = q / 8 * 1.6 * pi pts.append(V((sx * (.09 + .07 * (1 - cos(a * .5))) * hd, .06 - .08 * sin(a), hc + .08 * hd + .09 * sin(a * .5 + .3) - .06 * q / 8))) self.tube('Horn_' + side, pts, [.03 * hd, .027 * hd, .022 * hd, .016 * hd, .004], 'bone', 'Head', 10, 30, groove=.08, lobes=3) # Tail on two bones. t0 = V((0, .13, H + .02)) self.tube('Tail', [t0, t0 + V((0, .14, -.08)), t0 + V((0, .26, -.22)), t0 + V((0, .3, -.4))], [.04, .032, .022, .01], 'fur', 'Tail.1', 12, 20, weight_fn=lambda t: blend('Pelvis', 'Tail.1', t * 4) if t < .25 else blend('Tail.1', 'Tail.2', (t - .25) / .5)) self.ell('Tail_Tuft', t0 + V((0, .3, -.43)), (.04, .04, .06), 'fur2', 'Tail.2', 10, 7, bumps=.25) self.extra_bones.append(('Tail.1', t0, t0 + V((0, .2, -.14)), 'Pelvis')) self.extra_bones.append(('Tail.2', t0 + V((0, .2, -.14)), t0 + V((0, .3, -.4)), 'Tail.1')) # ------------------------------------------------------------ rig def rig(self): for ob in self.parts: bm = bmesh.new() bm.from_mesh(ob.data) bmesh.ops.remove_doubles(bm, verts=list(bm.verts), dist=.0000001) bmesh.ops.triangulate(bm, faces=list(bm.faces)) bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) if bm.calc_volume(signed=True) < 0: bmesh.ops.reverse_faces(bm, faces=list(bm.faces)) bm.to_mesh(ob.data) bm.free() ob.data.update() bpy.ops.object.armature_add(enter_editmode=True) rig = bpy.context.object rig.name = 'PeopleRig' ed = rig.data.edit_bones ed.remove(ed[0]) X, s = self.X, self.s def bone(name, head, tail, parent=None): b = ed.new(name) b.head, b.tail = head, tail if parent: b.parent = ed[parent] self.defs[name] = (V(head), V(tail), parent) H = self.hip bone('Root', (0, 0, 0), (0, 0, .15 * s)) bone('Pelvis', X((0, .0, H - .06)), X((0, .0, self.waist)), 'Root') bone('Spine', X((0, .0, self.waist)), X((0, .0, self.chest)), 'Pelvis') bone('Chest', X((0, .0, self.chest)), X((0, .0, self.neck)), 'Spine') bone('Neck', X((0, .0, self.neck)), X((0, -.01, self.head_c - .05)), 'Chest') bone('Head', X((0, -.01, self.head_c - .05)), X((0, -.01, self.head_c + .12 * self.sp['head'])), 'Neck') for side, sx in SIDES: bone('Clavicle.' + side, X((sx * .05, .012, self.shoulder - .02)), self.sh[side], 'Chest') bone('UpperArm.' + side, self.sh[side], self.elbow[side], 'Clavicle.' + side) bone('Forearm.' + side, self.elbow[side], self.W[side], 'UpperArm.' + side) fore = (self.W[side] - self.elbow[side]).normalized() bone('Hand.' + side, self.W[side], self.W[side] + fore * .08 * s, 'Forearm.' + side) hp, kn, an, toe = self.leg[side] bone('Thigh.' + side, X(hp), X(kn), 'Pelvis') bone('Shin.' + side, X(kn), X(an), 'Thigh.' + side) bone('Foot.' + side, X(an), X(toe), 'Shin.' + side) # Grip bones: the hands follow these; the weapon meshes are weighted to them. if self.weapon_parent == 'Offhand': order = [('Offhand', 'Chest'), ('Weapon', 'Offhand')] elif self.target['L'] == 'Weapon': order = [('Weapon', 'Chest'), ('Offhand', 'Weapon')] else: order = [('Weapon', 'Chest'), ('Offhand', 'Chest')] for name, parent in order: side = 'R' if name == 'Weapon' else 'L' bone(name, self.G[side], self.G[side] + self.u[side] * .25 * s, parent) if self.sp['style'] == 'bow': aim = (self.G['L'] - self.G['R']).normalized() bone('Arrow', self.G['R'], self.G['R'] + aim * .3 * s, 'Weapon') self.scaled.add('Arrow') for name, head, tail, parent in self.extra_bones: bone(name, X(head), X(tail), parent) bpy.ops.object.mode_set(mode='OBJECT') for ob in self.parts: mod = ob.modifiers.new('Weighted body', 'ARMATURE') mod.object = rig ob.parent = rig self.rig_ob = rig def pose_rotation(self, pb, r): q = Quaternion((0, 0, 1), r[2]) @ Quaternion((0, 1, 0), r[1]) @ Quaternion((1, 0, 0), r[0]) basis = pb.bone.matrix_local.to_quaternion() return basis.inverted() @ q @ basis def aim_bone(self, name, head, tail): pb = self.rig_ob.pose.bones[name] rest = pb.bone.matrix_local direction = self.defs[name][1] - self.defs[name][0] rotation = direction.rotation_difference(tail - head).to_matrix().to_4x4() pose = rotation @ rest pose.translation = head pb.matrix = pose bpy.context.view_layer.update() def delta(self, name): return self.rig_ob.pose.bones[name].matrix @ self.rig_ob.data.bones[name].matrix_local.inverted() # ------------------------------------------------------------ animation def animate(self, name, t, dur): rs, locs, scl = {}, {}, {} s, L = self.s, self.L st = self.sp['style'] def rot(b, x=0, y=0, z=0): old = rs.get(b, (0, 0, 0)) rs[b] = (old[0] + x, old[1] + y, old[2] + z) def loc(b, x=0, y=0, z=0): old = locs.get(b, (0, 0, 0)) locs[b] = (old[0] + x, old[1] + y, old[2] + z) flap = 0.0 if name == 'idle': breath = sin(2 * pi * t / dur) rot('Spine', .012 * breath) rot('Chest', -.014 * breath, 0, .006 * breath) rot('Head', .01 * breath, 0, -.02 * sin(2 * pi * t / dur)) loc('Weapon', 0, 0, .006 * L * breath) loc('Offhand', 0, 0, .005 * L * sin(2 * pi * t / dur + 1)) flap = sin(2 * pi * t * 4) if self.hover: loc('Root', 0, 0, .025 * s * sin(2 * pi * t * 2 / dur)) if st == 'bow': loc('Offhand', 0, .14 * L, -.28 * L) rot('Offhand', .35) elif name in ('walk', 'start', 'stop'): fade = 1 if name == 'walk' else sin(pi * .5 * t / dur) if name == 'start' else cos(pi * .5 * t / dur) ph = 2 * pi * t / 1.1 for side, sx in SIDES: q = ph + (0 if side == 'L' else pi) if self.hover: rot('Thigh.' + side, -.12 * sin(q) * fade) rot('Shin.' + side, .18 * fade) continue thigh = -.42 * sin(q) * fade knee = .62 * max(0.0, cos(q)) * fade + .06 * fade if self.sp.get('digitigrade'): knee = -knee rot('Thigh.' + side, thigh) rot('Shin.' + side, knee) rot('Foot.' + side, -(thigh + knee) * .6) rot('Pelvis', 0, 0, .05 * sin(ph) * fade) rot('Spine', .03 * fade, 0, -.04 * sin(ph) * fade) rot('Chest', 0, 0, -.02 * sin(ph) * fade) loc('Root', 0, 0, (-.018 * s * (1 - cos(2 * ph)) * .5 if not self.hover else .02 * s * sin(2 * ph)) * fade) loc('Weapon', 0, .015 * L * sin(ph) * fade, .012 * L * sin(2 * ph) * fade) loc('Offhand', 0, -.015 * L * sin(ph) * fade, .01 * L * sin(2 * ph + 1) * fade) flap = sin(2 * pi * t * 6 / 1.1) if st == 'bow': loc('Offhand', 0, .14 * L, -.28 * L) rot('Offhand', .35) rot('Cloak', .12 * fade + .04 * sin(2 * ph) * fade) elif name in ('attack_a', 'attack_b', 'ability'): self.attack(name, t, rot, loc, scl) flap = sin(2 * pi * t * 5) elif name == 'hit': q = curve(t, [(0, 0), (.066667, 1), (.2, .4), (.5, 0)]) rot('Spine', -.1 * q, 0, -.05 * q) rot('Chest', -.06 * q) rot('Head', -.14 * q, 0, .08 * q) loc('Root', 0, .04 * s * q, 0) loc('Weapon', 0, .04 * L * q, .03 * L * q) loc('Offhand', 0, .03 * L * q, .02 * L * q) flap = sin(2 * pi * t * 6) if st == 'bow': loc('Offhand', 0, .14 * L * (1 - q), -.28 * L * (1 - q)) rot('Offhand', .35 * (1 - q)) elif name == 'death': q = curve(t, [(0, 0), (.3, .12), (.8, .6), (1.3, 1), (1.5, .97), (2, 1)]) rot('Root', -1.38 * q) drop = self.hover if self.hover else 0.0 loc('Root', 0, .02 * s * q, (.05 * s - drop) * q) rot('Spine', .1 * q) rot('Head', -.25 * q, 0, .2 * q) loc('Weapon', 0, .08 * L * q, -.06 * L * q) loc('Offhand', 0, .06 * L * q, -.08 * L * q) for side, sx in SIDES: rot('Thigh.' + side, -.35 * q) rot('Shin.' + side, (.55 if not self.sp.get('digitigrade') else -.4) * q) flap = sin(2 * pi * t * 6) * (1 - q) rot('Wing.L', 0, -.4 * q) rot('Wing.R', 0, .4 * q) if 'Wing.L' in self.defs: rot('Wing.L', 0, .45 * flap, -.2 * flap) rot('Wing.R', 0, -.45 * flap, .2 * flap) if 'Tail.1' in self.defs: period = dur if name in ('idle', 'walk') else 1.3 rot('Tail.1', .1 * sin(2 * pi * t / period), 0, .3 * sin(2 * pi * t / period)) rot('Tail.2', .05 * sin(2 * pi * t / period), 0, .4 * sin(2 * pi * t / period - .8)) self.apply(rs, locs, scl, name, t) def attack(self, name, t, rot, loc, scl): st, s, L = self.sp['style'], self.s, self.L alt, charged = name == 'attack_b', name == 'ability' if st in ('blade_shield', 'axe'): if charged and st == 'blade_shield': bash = curve(t, [(0, 0), (.55, -.45), (.82, -.45), (.9, 1), (1.05, 1), (1.3, 0)]) loc('Offhand', .02 * L * bash, -.16 * L * max(0.0, bash) + .06 * L * max(0.0, -bash), .02 * L * abs(bash)) loc('Weapon', 0, .06 * L * abs(bash), .05 * L * abs(bash)) rot('Chest', 0, 0, .28 * bash) rot('Spine', .08 * max(0.0, bash)) loc('Root', 0, -.1 * s * max(0.0, bash), 0) rot('Thigh.L', -.25 * max(0.0, bash)) rot('Shin.L', .2 * max(0.0, bash)) return if charged: # War cry: both arms up, head back, then a forward stamp. cry = curve(t, [(0, 0), (.5, 1), (.85, 1), (.9, .4), (1.1, .2), (1.3, 0)]) loc('Weapon', -.05 * L * cry, .06 * L * cry, .4 * L * cry) rot('Weapon', -.5 * cry) loc('Offhand', .05 * L * cry, .05 * L * cry, .42 * L * cry) rot('Head', -.3 * cry) rot('Chest', -.12 * cry) stamp = curve(t, [(0, 0), (.8, 0), (.9, 1), (1.1, 1), (1.3, 0)]) rot('Thigh.R', -.3 * stamp) loc('Root', 0, -.06 * s * stamp, 0) return if not alt: up = curve(t, [(0, 0), (.45, 1), (.78, 1), (.9, -.7), (1.05, -.7), (1.3, 0)]) rot('Weapon', -1.3 * up, 0, -.3 * up) if up > 0: loc('Weapon', -.04 * L * up, .1 * L * up, .3 * L * up) else: loc('Weapon', -.08 * L * up, .16 * L * up, .02 * L * up) rot('Chest', -.1 * min(0.0, up), 0, .28 * up) rot('Spine', -.06 * max(0.0, up) + .1 * max(0.0, -up)) if st == 'axe': loc('Offhand', 0, .1 * L * max(0.0, up), .05 * L * max(0.0, up)) else: side = curve(t, [(0, 0), (.45, 1), (.78, 1), (.9, -.8), (1.05, -.8), (1.3, 0)]) sgn = 1 if st == 'blade_shield' else -1 rot('Weapon', .5 * abs(side), 1.25 * side * sgn, 0) loc('Weapon', .14 * L * side * sgn * -1, .06 * L * side, .1 * L * abs(side)) rot('Chest', 0, 0, .32 * side * sgn) rot('Thigh.R', .15 * curve(t, [(0, 0), (.45, 1), (.9, -1), (1.3, 0)])) rot('Thigh.L', -.2 * curve(t, [(0, 0), (.45, 0), (.9, 1), (1.3, 0)])) rot('Shin.L', .2 * curve(t, [(0, 0), (.45, 0), (.9, 1), (1.3, 0)])) elif st == 'club2h': if not alt: amp = 1.12 if charged else 1.0 up = curve(t, [(0, 0), (.5, 1), (.8, 1), (.9, -.6), (1.1, -.6), (1.3, 0)]) rot('Weapon', -1.25 * up * amp) if up > 0: loc('Weapon', .04 * L * up, .08 * L * up, .3 * L * up * amp) else: loc('Weapon', 0, .12 * L * up, .08 * L * up) rot('Spine', -.16 * max(0.0, up) + .14 * max(0.0, -up)) rot('Chest', -.08 * max(0.0, up) + .1 * max(0.0, -up)) if charged: loc('Root', 0, 0, -.06 * s * max(0.0, -up)) for side, _ in SIDES: rot('Thigh.' + side, -.2 * max(0.0, -up)) rot('Shin.' + side, .35 * max(0.0, -up)) else: side = curve(t, [(0, 0), (.5, 1), (.8, 1), (.9, -.8), (1.1, -.8), (1.3, 0)]) rot('Weapon', .35 * abs(side), -1.0 * side, 0) loc('Weapon', -.12 * L * side, .06 * L * side, .06 * L * abs(side)) rot('Chest', 0, 0, -.3 * side) rot('Spine', 0, 0, -.12 * side) elif st == 'bow': raise_bow = curve(t, [(0, 0), (.25, 1), (1.12, 1), (1.3, 0)]) loc('Offhand', 0, .14 * L * (1 - raise_bow), -.28 * L * (1 - raise_bow)) rot('Offhand', .35 * (1 - raise_bow), -.3 * raise_bow if alt else 0, 0) full = .45 if not charged else .5 draw = curve(t, [(0, 0), (.3, 0), (.82, 1), (.9, 1), (.933333, 1.12), (1.1, 1.12), (1.28, 0), (1.3, 0)]) loc('Weapon', 0, full * L * min(draw, 1.0), .02 * L * max(0.0, draw - 1) * 8) rot('Chest', 0, 0, -.25 * raise_bow) rot('Head', 0, 0, .35 * raise_bow) rot('Spine', -.04 * raise_bow if not charged else -.08 * raise_bow) scale = 1.0 if t < .9 or t > 1.22 else .001 scl['Arrow'] = (scale, scale, scale) elif st == 'staff': if charged: up = curve(t, [(0, 0), (.6, 1), (.85, 1), (.9, 1.1), (1.1, 1), (1.3, 0)]) loc('Weapon', -.04 * L * up, .05 * L * up, .36 * L * up) loc('Offhand', .04 * L * up, .02 * L * up, .34 * L * up) rot('Offhand', -.8 * up) rot('Head', -.2 * up) loc('Root', 0, 0, .08 * s * up) elif not alt: th = curve(t, [(0, 0), (.5, -.5), (.8, -.6), (.9, 1), (1.05, 1), (1.3, 0)]) loc('Weapon', 0, -.2 * L * th, .06 * L * abs(th)) rot('Weapon', .5 * th) loc('Offhand', 0, -.12 * L * max(0.0, th), .08 * L * max(0.0, th)) rot('Chest', 0, 0, -.15 * th) else: cast = curve(t, [(0, 0), (.5, -.4), (.82, -.5), (.9, 1), (1.05, 1), (1.3, 0)]) loc('Offhand', .04 * L * cast, -.2 * L * max(0.0, cast) + .08 * L * max(0.0, -cast), .1 * L * abs(cast)) rot('Offhand', -.4 * max(0.0, cast)) rot('Chest', 0, 0, .2 * cast) elif st == 'claws': if charged: pounce = curve(t, [(0, 0), (.55, -.5), (.8, -.6), (.9, 1), (1.05, 1), (1.3, 0)]) loc('Root', 0, -.35 * s * max(0.0, pounce), -.05 * s * max(0.0, -pounce) + .04 * s * max(0.0, pounce)) rot('Spine', .2 * max(0.0, pounce) - .1 * max(0.0, -pounce)) for side, sx in SIDES: b = 'Weapon' if side == 'R' else 'Offhand' loc(b, 0, -.1 * L * max(0.0, pounce) + .08 * L * max(0.0, -pounce), .25 * L * max(0.0, -pounce) - .1 * L * max(0.0, pounce)) rot(b, .6 * max(0.0, pounce)) rot('Thigh.' + side, .3 * max(0.0, -pounce) - .2 * max(0.0, pounce)) rot('Shin.' + side, .4 * max(0.0, -pounce)) else: b, sx = ('Weapon', -1) if not alt else ('Offhand', 1) sw = curve(t, [(0, 0), (.5, 1), (.8, 1), (.9, -.8), (1.05, -.8), (1.3, 0)]) if sw > 0: loc(b, sx * .12 * L * sw, .1 * L * sw, .28 * L * sw) else: loc(b, sx * .16 * L * sw, .12 * L * sw, .12 * L * sw) rot(b, -.6 * sw, 0, -sx * .4 * sw) rot('Chest', .08 * max(0.0, -sw), 0, -sx * .3 * sw) rot('Spine', .06 * max(0.0, -sw)) loc('Root', 0, -.05 * s * max(0.0, -sw), 0) def apply(self, rs, locs, scl, name, t): rig = self.rig_ob for pb in rig.pose.bones: pb.rotation_mode = 'QUATERNION' pb.rotation_quaternion = self.pose_rotation(pb, rs.get(pb.name, (0, 0, 0))) pb.location = pb.bone.matrix_local.to_3x3().inverted() @ V(locs.get(pb.name, (0, 0, 0))) pb.scale = scl.get(pb.name, (1, 1, 1)) bpy.context.view_layer.update() chest = self.delta('Chest') for side, _ in SIDES: target = self.delta(self.target[side]) c = target @ self.W[side] a = rig.pose.bones['UpperArm.' + side].head.copy() elbow, reach, error = ik_elbow(a, c, self.l1, self.l2, chest @ self.pole[side]) self.aim_bone('UpperArm.' + side, a, elbow) self.aim_bone('Forearm.' + side, elbow, reach) hand = target @ rig.data.bones['Hand.' + side].matrix_local hand.translation = c rig.pose.bones['Hand.' + side].matrix = hand bpy.context.view_layer.update() self.errors.append((error, name, round(t, 3), side)) def bake(self): rig = self.rig_ob for name, (dur, loop) in CLIPS.items(): rig.animation_data_clear() rig.animation_data_create() action = bpy.data.actions.new(name) action.use_fake_user = True rig.animation_data.action = action for frame in range(round(dur * 30) + 1): self.animate(name, frame / 30, dur) for pb in rig.pose.bones: pb.keyframe_insert('rotation_quaternion', frame=frame) pb.keyframe_insert('location', frame=frame) if pb.name in self.scaled: pb.keyframe_insert('scale', frame=frame) action['loop'] = loop action['contact_seconds'] = CONTACT if name in ('attack_a', 'attack_b', 'ability') else -1 for layer in action.layers: for strip in layer.strips: for bag in strip.channelbags: for fc in bag.fcurves: for key in fc.keyframe_points: key.interpolation = 'LINEAR' rig.animation_data_clear() rig.animation_data_create() rig.animation_data.action = bpy.data.actions['idle'] bpy.context.scene.render.fps = 30 bpy.context.scene.frame_set(0) def export(self): bpy.ops.wm.save_as_mainfile(filepath=str(SRC / ('peoples/%s.blend' % self.key))) source_parts = len(self.parts) bpy.ops.object.select_all(action='DESELECT') for ob in self.parts: ob.select_set(True) bpy.context.view_layer.objects.active = self.parts[0] bpy.ops.object.join() body = bpy.context.object body.name = '%sBody' % self.sp['tag'] body.select_set(True) self.rig_ob.select_set(True) bpy.context.view_layer.objects.active = self.rig_ob path = OUT / ('%s.glb' % self.key) bpy.ops.export_scene.gltf(filepath=str(path), export_format='GLB', use_selection=True, export_animations=True, export_animation_mode='ACTIONS', export_force_sampling=True, export_frame_range=False, export_anim_slide_to_zero=True, export_skins=True, export_all_influences=False, export_def_bones=True, export_anim_single_armature=True, export_materials='EXPORT', export_yup=True) raw = path.read_bytes() doc = json.loads(raw[20:20 + struct.unpack_from('