Completed Primitive Drawing Module

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
250
#1
This MCM module recycles parts of the sword trail drawing function using a couple of projection mods. The resulting functions have been used to create a light-weight library that allows developers to draw various primitive types.

To make use of the functions in this library, install the DOL mod with the latest version of Melee Code Manager.

Code:
-==-

Primitive Drawing Module
Use included functions to send GX FIFO stack vertex information for drawing primitives.
See ( https://smashboards.com/threads/primitive-drawing-module.454232/ ) for more info.
[Punkline]
1.02 ----- 0x800c2684 --- 386000013880000438a0000538c00005 -> 57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 --- 386000013880000338a00000 ->57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
1.02 ----- 0x800c2da0 --- 38b80001 -> branch
b <projection_return>
00000000
1.02 ----- 0x800c2674 --- 38600001 -> branch
bl <prim_default_swordtrails>
7C8802A6 7C6444AA
bl <prim_setup>
881C2101
b 0x800c2738
60000000
1.02 ----- 0x800c2734 --- 881c2101 -> branch
BBC10010
b <projection_return>
60000000
1.02 ----- 0x800c2d3c --- 4827968d -> branch
bl <prim_mtx_setup>
38B80001
b 0x800c2da4
60000000
<prim_setup> 1.02
7C0802A6 90010004 9421C000 BFC10010 7C7E1B78 7C9F2378 38600001
b 0x800c2678
<prim_mtx_setup> 1.02
7C0802A6 90010004 9421C000
bl 0x8033C3C8
b  0x800c2d40
<projection_return> All
38214000 80010004 7C0803A6 4E800020
<GXBegin> 1.02
b 0x8033D0DC
<GXSetLineWidth> 1.02
b 0x8033D240
<GXSetPointSize> 1.02
b 0x8033d298
<prim_close> 1.02
3860FFFF
b 0x80361fc4
<prim_main> All
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7D0802A6 80A80000 7C842B78 80A80004 3CC00010 60C61300 38E01455 7CC845AA
bl <prim_main_parametric>
38210008 80010004 7C0803A6 4E800020
<prim_main_parametric> All
7C0802A6 90010004 9421FFC0 BFA10010 7C7E1B78 7C9F2378 7C832378 7CA42B78
bl <prim_setup>
bl <prim_mtx_setup>
57E3063E 2C030005 41A00024 2C030007 4181001C 57E385BE 38800005 4182000C
bl <GXSetLineWidth>
48000008
bl <GXSetPointSize>
57E31E38 38630080 38800000 57C5043E
bl     <GXBegin>
3C60CC01 38638000 BBA10010 38210040 80010004 7C0803A6 4E800020
<prim_quads> All
38800000
b <prim_main>
<prim_triangles> All
38800002
b <prim_main>
<prim_trianglestrip> All
38800003
b <prim_main>
<prim_trianglefan> All
38800004
b <prim_main>
<prim_lines> All
38800005
b <prim_main>
<prim_linestrip> All
38800006
b <prim_main>
<prim_points> All
38800007
b <prim_main>
<prim_defaults>
4E800021 00101300 00001455
<prim_default_swordtrails>
4E800021 00101300 00001455
<prim_toggle_z>
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7C6802A6 80830000 68841000 90830000 38210008 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Primitive Drawing Module
Use included functions to send GX FIFO stack vertex information for drawing primitives.
See ( https://smashboards.com/threads/primitive-drawing-module.454232/ ) for more info.
[Punkline]
1.02 ----- 0x800c2684 --- 386000013880000438a0000538c00005 -> 57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 --- 386000013880000338a00000 ->57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
# these overwrites convert loaded immediates into rlwinms
# -- this allows the primitives to be drawn somewhat parametrically


1.02 ----- 0x800c2da0 --- 38b80001 -> branch
# projection end <prim_mtx_setup>
b <projection_return>
.long 0


1.02 ----- 0x800c2674 --- 38600001 -> branch
# -- projection start for <prim_setup>
# External prolog fabricates an interface for the
# purpose of creating parameters in the setup process:
#    r3       r4
# C0000000 00000000 = r3 for GXSetCullMode
# 03FF0000 00000000 =  (line width, or point size)
# 00002000 00000000 = r3 for GXSetCompLoc
# 00001000 00000000 = r3 for GXSetZMode
# 00000800 00000000 = r5 for GXSetZMode
# 00000700 00000000 = r4 for GXSetZMode
# 000000FF 00000000 =  (primitive type)
# 00000000 00003000 = r3 for GXSetBlendMode
# 00000000 00000700 = r4 for GXSetBlendMode
# 00000000 00000070 = r5 for GXSetBlendMode
# 00000000 0000000F = r6 for GXSetBlendMode

# 00101303 00001455 = defaults

# each paraam is parsed by an rlwinm (see static overwrites)
# (line width and primite type are not parsed here)

bl <prim_default_swordtrails>
mflr r4
lswi r3, r4, 0x8
# load sword trail parameters for natural trail drawing
# modifying these params will change the way sword trails are drawn

bl <prim_setup>
lbz r0, 0x2101 (r28) # repair INJ2 hook instruction externally
b 0x800c2738
nop


1.02 ----- 0x800c2734 --- 881c2101 -> branch
# projection end <prim_setup>
# lbz r0, 0x2101 (r28)
lmw r30, 0x10(sp)
b <projection_return>
nop


1.02 ----- 0x800c2d3c --- 4827968d -> branch
# projection start <prim_mtx_setup>
bl <prim_mtx_setup>
addi r5, r24, 1
b 0x800c2da4
nop


<prim_setup> 1.02
# projection handle
# r3 = params 1 = C WWW ZZ PP  (W is unused here)
# r4 = params 2 = ---- BBBB
# r30 and r31 are safe to use for projection duration
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x4000(sp)
stmw r30, 0x10(sp)

mr r30, r3  # Static overwrite strings will create rlwinms that extract from r30 and r31.
mr r31, r4  # These will replace some hardcoded arguments made for GX function calls

li r3, 1    # original instruction; arg for GXSetColorUpdate
b 0x800c2678


<prim_mtx_setup> 1.02
# projection handle
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x4000(sp)

bl 0x8033C3C8    # GXClearVtxDesc -- original instruction
b  0x800c2d40


<projection_return> All
# reusable return function
addi  sp, sp, 0x4000
lwz  r0, 0x4(sp)
mtlr r0
blr


<GXBegin> 1.02
b 0x8033D0DC # portable function call


<GXSetLineWidth> 1.02
b 0x8033D240


<GXSetPointSize> 1.02
b 0x8033d298


<prim_close> 1.02
li r3, -1
b 0x80361fc4


<prim_main> All
# r3 = vert count
# r4 = primitive type
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x8(sp)
bl <prim_defaults>
mflr r8
lwz r5, 0(r8)
or r4, r4, r5
lwz r5, 4(r8)
# load params

lis r6,      0x0010
ori r6, r6,  0x1300
li   r7,     0x1455
stswi r6, r8, 0x8
# reset params to default for next draw

bl <prim_main_parametric>

_return:
addi sp, sp, 8
lwz r0, 0x4(sp)
mtlr r0
blr



<prim_main_parametric> All
# r3 = vert count
#    r4        r5
# C0000000  00000000 = Cull Mode
#  +8 = cull backface
#  +4 = cull frontface

# 03FF0000  00000000 = Line Width/Point Size
#   1/16 pixel increments; 0x2A8 maximum width

# 00002000  00000000 = Compare Location
#  +1 = z buffer compares before texturing

# 00001000  00000000 = Z Buffer Compare
#  +1 = z buffer enables compare

# 00000800  00000000 = Z Buffer Update
#  +1 = z buffer enables update

# 00000700  00000000 = Z Buffer Comparison Logic
#  +4 = greater than
#  +2 = equal to
#  +1 = less than

# 000000FF  00000000 = Primitive Type
#   0 = quads
#   1 = --
#   2 = triangles
#   3 = trianglestrip
#   4 = trianglefan
#   5 = lines
#   6 = linestrip
#   7 = points

# 00000000  00003000 = Blend Type
#   0 = None  -- write input directly to EFB
#   1 = Blend -- blend using blending equation
#   2 = Logic -- blend using bitwise operation
#   3 = Subtract -- input subtracts from existing pixel

# 00000000  00000700 = Blend Source
#   0 = zero -- 0.0
#   1 = one  -- 1.0
#   2 = source color
#   3 = inverted source color
#   4 = source alpha
#   5 = inverted source alpha
#   6 = destination alpha
#   7 = inverted destination alpha

# 00000000  00000070 = Blend Dest
#   (see above)

# 00000000  0000000F = Blend Logic
#   0 -- CLEAR;   dst = 0
#   1 -- AND;     dst = src & dst
#   2 -- REVAND;  dst = src & ~dst
#   3 -- COPY;    dst = src
#   4 -- INVAND;  dst = ~src & dst
#   5 -- NOOP;    dst = dst
#   6 -- XOR;     dst = src ^ dst
#   7 -- OR;      dst = src | dst
#   8 -- NOR;     dst = ~(src | dst)
#   9 -- EQUIV;   dst = ~(src ^ dst)
#   A -- INV;     dst = ~dst
#   B -- REVOR;   dst = src | ~dst
#   C -- INVCOPY; dst = ~src
#   D -- INVOR;   dst = ~src | dst
#   E -- NAND;    dst = ~(src & dst)
#   F -- SET;     dst = 1

_macros:
.macro countShift  m, c
  # countShift = recursive macro updates symbol "maskShift"
  # maskShift  = number of 0 bits padding first mask bit (from right to left)

  # <- termination condition allows us to escape loop
  .if \m&1               # if (mask & 0x00000001) is not == 0
    .set maskShift, \c     # maskshift = count   -- termination

    # <- recursive macro call allows us to iterate loop
    .else                  # else
    .set maskShift, \c+1   # maskshift = count++ -- incr counter
    countShift \m>>1, \c+1 # call self

  .endif # close if block
.endm  # close macro block

# macro "countShift" can be used to count the zero bits
# resulting symbol "maskShift" can be used to imply shift amount in rlwinm and rlwimi syntax

.macro mext  rD, rS, m
# mext : masked extract
  countShift \m, 0    # m = 32-bit mask
  rlwinm \rD, \rS, (32-maskShift)%32, \m>>maskShift
.endm

mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x40(sp)
stmw r29, 0x10(sp)

mr r30, r3 # save r3 and r4 for after projection calls
mr r31, r4 #
mr r3,  r4 # r3 = params 1
mr r4,  r5 # r4 = params 2

bl <prim_setup>     # set up params
bl <prim_mtx_setup> # set up mtx

mext   r3, r31, 0x000000FF  # r3 = primitive ID
cmpwi  r3, 5                 # if it's larger than
blt+ _end_width_handle
  cmpwi  r3, 7
  bgt- _end_width_handle
    mext   r3, r31, 0x03FF0000
    li     r4, 5
    beq- _point_handle

    _width_handle: # exclusively for lines and linestrips
    bl <GXSetLineWidth>
    b _end_width_handle

    _point_handle: # exclusively for points
    bl <GXSetPointSize>

_end_width_handle:

rlwinm  r3, r31, 3, 0x000000F8
addi   r3, r3, 0x80  # r3 is now a primitive ID that can be fed to GXBegin
li     r4, 0         # use vtx format 0
mext   r5, r30, 0x0000FFFF # use N number of vertices
bl     <GXBegin>

lis  r3, 0xCC01
addi r3, r3, -0x8000 # r3 = address of hardware FIFO stack
# write vertex(X, Y, Z, C) to address in r3
# each write must be to the exact same address

lmw  r29, 0x10(sp)
addi sp, sp, 0x40
lwz  r0, 0x4(sp)
mtlr r0
blr


# convenience functions:
<prim_quads> All
li r4, 0
b <prim_main>


<prim_triangles> All
li r4, 2
b <prim_main>


<prim_trianglestrip> All
li r4, 3
b <prim_main>


<prim_trianglefan> All
li r4, 4
b <prim_main>


<prim_lines> All
li r4, 5
b <prim_main>


<prim_linestrip> All
li r4, 6
b <prim_main>


<prim_points> All
li r4, 7
b <prim_main>

<prim_defaults>
blrl
.long 0x00101300
.long 0x00001455

<prim_default_swordtrails>
blrl
.long 0x00101300
.long 0x00001455

<prim_toggle_z>
# this function simply toggle the z buffer comparison in the Z buffer options
# it's meant to make it easy for people who just want to draw on top of things
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x8(sp)
bl <prim_defaults>
mflr r3
lwz r4, 0(r3)
xori r4, r4, 0x1000 # toggle z comparison bit
stw r4, 0(r3)
addi sp, sp, 8
lwz r0, 4(sp)
mtlr r0
blr

---

Example codes:

- ECB Anchor Test 2
- ECB Anchor Test 1
- Player JObj tracer
- Colored Player Skeletons

---

With this module installed, developers can make use of the following functions for the purpose of drawing to the EFB:

bl <prim_quads>
# draw a series of individual quadrilateral 4-point polygons

bl <prim_triangles>
# draw a series of individual triangle 3-point polygons

bl <prim_trianglestrip>
# draw a contiguous strip of connecting 3-point triangles that share an edge for each connection in the sequence.

bl <prim_trianglefan>
# draw a triangle strip where one side of the strip circles around a central vertex

bl <prim_lines>
# draw a series of individual lines using 2 points per line. Line width is the same for each line in the series.

bl <prim_linestrip>
# draw a contiguous strip of connecting 2-point lines

bl <prim_points>
# draw a series of individual points, using single vertices. Point size is the same for each point in the series.

Each of the above functions only require the argument:
r3 = number of vertices

When the function returns, you may use the address returned in r3 to store vertices.
note - when storing vertex information, all data goes to the exact same address; 0(r3)

To describe a vertex, store the following values to 0(r3) in a sequence:
1 - floating point single = vertex X
2 - floating point single = vertex Y
3 - floating point single = vertex Z
4 - 32-bit UINT = RGBA color

---

The following two functions can be called without arguments:

bl <prim_close>
# use this at the end of your drawing to prevent params from corrupting the next GFX drawing in the game

bl <prim_toggle_z>
# use this before starting a primitive to toggle the zbuffer comparison on or off. If off, primitives will be drawn on top of other geometry. On by default.

bl <prim_main>
# r3 = vert count
# r4 = primitive type -- used to provide all of the above basic functions
# this call creates parameters similar to the basic sword trail drawings by default
# <prim_defaults> can be modified to change the params for the next drawn primitive
# primitive types for r4 argument
# 0 - quads
# 1 - (quads2)
# 2 - triangles
# 3 - trianglestrip
# 4 - trianglefan
# 5 - lines
# 6 - linestrip
# 7 - points

bl <prim_defaults>
# this blrl table can be accessed with an mflr instruction upon return.
# it may be modified in order to change the parameters of the nex <prim_main> call
# parameters will be reverted after call to <prim_main>
# see the following function for details about params structure

bl <prim_main_parametric>
# this version of the function lets you specify the parameters directly using immediates
# r3 = vert count
# r4 = params 1
# r5 = params 2

# the parameters are encoded like so:

# r4 r5
# C0000000 00000000 = Cull Mode
# +8 = cull backface
# +4 = cull frontface

# 03FF0000 00000000 = Line Width/Point Size
# 1/16 pixel increments; 0x2A8 maximum width
# only used for points and lines; otherwise ignored

# 00002000 00000000 = Compare Location
# +1 = z buffer compares before texturing
# 00001000 00000000 = Z Buffer Compare
# +1 = z buffer enables compare

# 00000800 00000000 = Z Buffer Update
# +1 = z buffer enables update

# 00000700 00000000 = Z Buffer Comparison Logic
# +4 = greater than
# +2 = equal to
# +1 = less than

# 000000FF 00000000 = Primitive Type
# 0 = quads
# 1 = --
# 2 = triangles
# 3 = trianglestrip
# 4 = trianglefan
# 5 = lines
# 6 = linestrip
# 7 = points

# 00000000 00003000 = Blend Type
# 0 = None -- write input directly to EFB
# 1 = Blend -- blend using blending equation
# 2 = Logic -- blend using bitwise operation
# 3 = Subtract -- input subtracts from existing pixel

# 00000000 00000700 = Blend Source
# 0 = zero -- 0.0
# 1 = one -- 1.0
# 2 = source color
# 3 = inverted source color
# 4 = source alpha
# 5 = inverted source alpha
# 6 = destination alpha
# 7 = inverted destination alpha

# 00000000 00000070 = Blend Dest
# (see above)

# 00000000 0000000F = Blend Logic
# 0 -- CLEAR; dst = 0
# 1 -- AND; dst = src & dst
# 2 -- REVAND; dst = src & ~dst
# 3 -- COPY; dst = src
# 4 -- INVAND; dst = ~src & dst
# 5 -- NOOP; dst = dst
# 6 -- XOR; dst = src ^ dst
# 7 -- OR; dst = src | dst
# 8 -- NOR; dst = ~(src | dst)
# 9 -- EQUIV; dst = ~(src ^ dst)
# A -- INV; dst = ~dst
# B -- REVOR; dst = src | ~dst
# C -- INVCOPY; dst = ~src
# D -- INVOR; dst = ~src | dst
# E -- NAND; dst = ~(src & dst)
# F -- SET; dst = 1

---

I will post some demonstrations soon, and link them in the example codes section.
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
250
#2
To demonstrate, I’ll be making a series of example codes. I have a few things in mind, so it’ll take me some time to come up with it all.

To start, here are a few codes that use the drawing module to trace JObjs with line and dot primitives.

---

ECB Anchor Test 1

Code:
-==-

ECB Anchor Visualization
primitive drawing test traces ECB anchor joints on players
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
2C1D0002 40A2004C
bl <prim_toggle_z>
38600007
bl <prim_linestrip>
809C002C 38000007 7C0903A6 3800FFFF 80A407F8 C0050050 D0030000 C0050060 D0030000 C0050070 D0030000 90030000 38840004 4200FFDC
bl <prim_close>
BB61008C 00000000
Code:
-==-
!
ECB Anchor Visualization
primitive drawing test traces ECB anchor joints on players
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
# this injection is in the middle of a drawing loop used by players
# it has the active camera set to the main camera, so drawing will appear on screen
# injection placement ensures it draws after the player
# r28 = player entity
# r29 = z loop iteration (out of 2)
cmpwi r29, 2
bne+ _return
# with this, our drawing will only happen after everything on the player is drawn

bl <prim_toggle_z>
# we want to make our line drawing appear on top of the player
# toggling z buffer comparison makes it so that it ignores intersection with player

li r3, 7  # r3 argument = number of vertices to draw
bl <prim_linestrip>
# returns r3 = fifo pipe address
# writing to this will let us create vertices
# we need to write to 0(r3) for every piece of information given to GX

lwz r4, 0x2C(r28)
li r0, 7
mtctr r0
# load player data, and set up a bdnz loop for 7 iterations
# this will catch each JObj we want to trace

li r0, -1
# r0 = 0xFFFFFFFF -- or the RGBA color white

_loop:
lwz  r5, 0x7F8(r4)
lfs  f0, 0x50(r5) # load X vert translation
stfs f0, 0(r3)    # write to GX FIFO
lfs  f0, 0x60(r5) # load Y
stfs f0, 0(r3)    # write Y
lfs  f0, 0x70(r5) # load Z
stfs f0, 0(r3)    # write Z
stw  r0, 0(r3)    # write RGBA color
# X, Y, Z, and color create a vertex

addi r4, r4, 4   # move to next JObj
bdnz+ _loop      # iterate loop
# loop will finish after 7 vertices have been drawn

bl <prim_close>
# finish the primitive with this

_return:
lmw    r27, 0x008C (sp)  # original instruction
.long 0

This code simply draws lines between the bones that anchor a player’s ECB quad. It’s useful for seeing how the ECB are influenced by the player skeleton. DRGN DRGN

Player data offset 0x7F8 is the start of a series of JObj pointers, which stand for “joint” objects. Joints carry information about where they are in 3D space using offsets 0x50, 0x60, and 0x70 to store an absolute translation coordinate as part of a transformation matrix.

By accessing the 7 joint pointers in a player using a loop, it’s possible to use these coordinates to draw the location of the bone. In this code, a simple default line primitive is created using <prim_linestrip> and 7 vertices are drawn using the X Y and Z translation coordinates of the corresponding JObjs:



By calling <prim_linestrip> with 7 in r3, we tell the GX that we’re ready to draw 7 vertices for a linestrip primitive. The default parameters are set up to be just like sword trails, with lines taking up 1 native pixel in rasterized width:



As you can see, the lines illustrate where the ECB joints are located, starting at the bottom with TopN. The lines are visible through the player because we called <prim_toggle_z> before <prim_linestrip>



---

Player JObj Tracer

Code:
-==-

Player JObj Tracer
This primitive drawing test includes a function that traces JObj skeletons.
By default, it's used to illustrate player joints.
[Punkline]
1.02 ----- 800c2660 --- 8383002c -> branch
8383002C 807C05E8 80630050
bl <illustrate_JObj_skeleton>
00000000
<illustrate_JObj_skeleton>
7C0802A6 90010004 9421FFE0 BFC10008 7C7F1B78 807F0010 2C030000 4180FFE5 807F0008 2C030000 4180FFD9
bl <prim_toggle_z>
38600002 38800005 3BC0FFFF 48000018
bl <prim_toggle_z>
38600001 38800007 3FC0FF00 63DE00FF
bl <prim_main>
C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93C30000 2C1EFFFF 40820028 83FF000C C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93C30000 4BFFFFA4
bl <prim_close>
BBC10008 38210020 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Player JObj Tracer
This primitive drawing test includes a function that traces JObj skeletons.
By default, it's used to illustrate player joints.
[Punkline]
1.02 ----- 800c2660 --- 8383002c -> branch
lwz    r28, 0x002C (r3) # original instruction

lwz r3, 0x5E8(r28)
lwz r3, 0x50(r3) # load 5th bone in skeleton (to skip TopN, TransN, XRotN, YRotN)
bl <illustrate_JObj_skeleton>
.long 0



<illustrate_JObj_skeleton>
# r3 = THIS JObj

_recursion:
# recursive function will call itself for each remaining JObj in the family
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x20(sp)
stmw  r30, 0x8(sp)
mr r31, r3

lwz r3, 0x10(r31)
# r3 = child
cmpwi r3, 0
bltl _recursion
# if  child exists, recursively call self

lwz r3, 0x8(r31)
# r3 = sibling
cmpwi r3, 0
bltl _recursion
# if sibling exists, recursively call self

_line:
bl <prim_toggle_z>
li r3, 2   # 2 verts per line
li r4, 5   # lines
li r30, -1 # white
# lines on first pass
# note these are not linestrips, because we can't predict the number of verts we need
b _draw

_point:
bl <prim_toggle_z>
li r3, 1         # 1 vert per point
li r4, 7         # points
lis r30, 0xFF00  # red
ori r30, r30, 0xFF
# the saved register makes it so we can check for second pass conclusion

_draw:
# r3 = vert count
# r4 = primitive type
#     type 5 = lines
#     type 7 = points
bl <prim_main>
# r3 = GX FIFO hardware address


lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r30, 0(r3)
# first JOBj vertex has been drawn

cmpwi r30, -1
bne _close
# if on second pass, don't draw second vert

lwz r31, 0xC(r31)
# load JObj parent

lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r30, 0(r3)
# second vertex has been draw, completing the line
# line should appear in the EFB for the next frame

b _point
# this branch is only made on the first pass

_close:
bl <prim_close>

_return:
lmw  r30, 0x8(sp)
addi sp, sp, 0x20
lwz  r0, 0x4(sp)
mtlr r0
blr

This code uses recursion to iterate through all bones in a player skeleton, and draws the child->parent relationships. Instead of just using lines, this code makes a second pass and creates tiny red dots where a player bone is located using point primitives.

The recursion is made possible by this structure:



There is a child <-> parent and sibling -> sibling link in each skeletal JObj. By creating a function that recursively checks for each child and sibling from a root joint, it’s possible to create something that acts on them all:



I was very surprised by the nature of player JObjs after observing this code. It seems that several bones in different player skeletons are not included in some animations -- causing them to stop being transformed and get left behind without the rest of the skeleton. This is really apparent in bowser’s jump animations, or any teleport move.


For some, untransformed bones are just for like, a falcon punch graphic, or cape model for captain falcon and mario respectively; but for others it seems to be ragdoll joint ends, and some other special joints. If they haven’t been included in ANY animations, the joints will appear at coordinates 0,0,0 on the stage:


For the drawing, the function <prim_main> is used to create a primitive type that’s specified by r4, allowing both lines and dots to be created from the same function:



---

Colored Player Skeletons

Code:
-==-

Colored Player Skeletons
This is a test of the primitive drawing module.
It draws lines between player joints that are moving, and dots where joints have stopped moving.
Many characters have joints that stop being animated after a move, or that never move from the beginning of a match.
[Punkline]
1.02 ----- 80373804 --- 39000088 -> 390000A0
1.02 ----- 800737e8 --- 39000088 -> 390000A0
1.02 ----- 80073718 --- 39000088 -> 390000A0
1.02 ----- 800304c8 --- 48338141 -> branch
806DC18C 83E30020 2C1F0000 418200A4 38800001 80FF0028 80C7009C 811F002C 806805E8 80A80894 90A7009C 7C062800 40A20014 80A80010 2C05000B 41820008 38800000 38841000 80630050 38A0FF00 88E8000C 2C070004 4080004C 38C004E1 1D070003 7CC64430 54C9C1CE 50C97BDE 50C935EE 1CA900FF 3D008045 61083130 1C070E90 7CC8002E 7C06F800 41A20018 55203032 7CA50278 3C003F3F 60003F00 7CA50278
bl <trace_JObj_skeleton>
83FF0010 4BFFFF5C
bl 0x80368608
00000000
<trace_JObj_skeleton> All
7C0802A6 90010004 9421FFD0 BF810008 7C7F1B78 7C9E2378 7CBC2B78 807F0010 2C030000 4180FFDD 7FC4F378 7F85E378 807F0008 2C030000 4180FFC9 395F0094 7CCA64AA 893F0093 807F0050 809F0060 80BF0070 73C00001 41820008 7C6A65AA 7D3CE378 57C5421E 38800005 7F033000 7F843800 4CC73202 7F854000 4CC73202 2F8900FF 7FA00026 41820030 419A0018 39690040 2C0B00FF 4081001C 396000FF 48000014 3969FFFE 2C0B0000 40800008 39600000 997F0093 38600002 41BE0018 40BA0014 38600001 57C5421E 3CA50018 38800007 7CA42378 60840300 38A01455
bl <prim_main_parametric>
C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93830000 7FAFF120 419E0008 419A0024 83FF000C C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93830000
bl <prim_close>
BB810008 38210030 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Colored Player Skeletons
This is a test of the primitive drawing module.
It draws lines between player joints that are moving, and dots where joints have stopped moving.
Many characters have joints that stop being animated after a move, or that never move from the beginning of a match.
[Punkline]
1.02 ----- 80373804 --- 39000088 -> 390000A0
1.02 ----- 800737e8 --- 39000088 -> 390000A0
1.02 ----- 80073718 --- 39000088 -> 390000A0
# JObj padding, 0x18 bytes are zeroed after JObj structure allocation
# this does not change the resulting allocation size, since it aligns to 32 bytes


1.02 ----- 800304c8 --- 48338141 -> branch
# bl ->0x80368608 --  HSD_CObjEndCurrent
# This is the end of a general match camera display function.
# It's the same function that hosts displaying ECB quads for players and items.
# The current HSD CObj = main camera entity CObj
# The original instruction dismisses the current CObj
# -- we fit in our drawing function before that.
lwz r3, -0x3e74(r13)
lwz r31,0x20(r3)
# r31 = THIS player

_for_each_player:
cmpwi r31, 0
beq- _return
# termination condition

li r4, 1
lwz r7, 0x28(r31)
lwz r6, 0x9C(r7)
lwz r8, 0x2C(r31)
lwz r3, 0x5E8(r8)
# r3 = player skeleton base index
# r4 = argument for updating JObj delta in draw iteration
# r6 = recorded action state frame (stored in root JObj padding)
# r7 = root JObj
# r8 = player data

lwz r5, 0x894(r8)
stw r5, 0x9C(r7)
# update recorded action state frame with new data from player entity

cmpw r6, r5
bne+ _draw_setup
# if action frame is different from record, then update XYZ pos for delta calc
# delta calc is true or false, and simply determines whether to draw a line or a dot
# dots will slowly disappear with a fading alpha

  lwz r5, 0x10(r8)
  cmpwi r5, 0xB
  beq- _draw_setup
  # if character is "sleeping" we'll force the update so the skeleton dots disappear

    li r4, 0
    # else, we leave the records alone for things like hitlag, or slowmo frames

_draw_setup:
addi r4, r4, 0x1000

lwz r3, 0x50(r3)
# r3 = load 5th bone in skeleton (to skip drawing TopN, TransN, XRotN, YRotN)

li r5, -256
# r27 = RGBA color; defaults to white
# FF FF FF 00 (alpha gets ORed in)

lbz r7, 0xC(r8)
cmpwi r7, 4
bge- _draw
# if player slot is not in range of controller ports, just use white color
# else, generate red, blue, yellow, or green according to player slot

li r6, 0b010011100001

# 3-bit BGR color array, indexed according to player colors

mulli r8, r7, 3
srw r6, r6, r8
# r6 = unmasked color

rlwinm r9, r6, 24, 7, 7    # 01 00 00 00  <-  01
rlwimi r9, r6, 15, 15, 15  # 00 01 00 00  <-  02
rlwimi r9, r6, 6, 23, 23   # 00 00 01 00  <-  04
mulli  r5, r9, 0xFF       #  R  G  B 00  *=  FF
# 8-bit channels, RGBA

lis r8, 0x8045
ori r8, r8, 0x3130
mulli r0, r7, 0xE90
lwzx r6, r8, r0
cmpw r6, r31
beq+ _draw
# if player doesn't match player slot, then character is a sub-player like nana
# we'll darken the color for this case

_sub_player:
slwi r0, r9, 6
xor r5, r5, r0
# mix 25% black to color

lis r0, 0x3f3f
ori r0, r0, 0x3f00
xor r5, r5, r0
# mix ~25% mid-gray to color

_draw:
# r3 = bone to start on
# r4 = condition for updating delta records
# r5 = color without a blank alpha channel
bl <trace_JObj_skeleton>

_iterate:
lwz r31, 0x10(r31)
b _for_each_player

_return:
bl 0x80368608
.long 0




<trace_JObj_skeleton> All
# r3 = THIS JObj
# r4 = params
# 0x00000001 = bool for updating delta record
# 0x0000FF00 = line width base
# r5 = color with no alpha channel
_recursion:
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x30(sp)
stmw r28, 0x8(sp)
mr   r31, r3
mr   r30, r4
mr   r28, r5

lwz r3, 0x10(r31)
cmpwi r3, 0
bltl _recursion
# if  child exists, recursively call self

mr  r4, r30
mr  r5, r28
lwz r3, 0x8(r31)
cmpwi r3, 0
bltl _recursion
# if sibling exists, recursively call self

# done with recursion
# before each stack frame collapses, the involved joints are drawn

addi r10, r31, 0x94
lswi r6, r10, 0xC
lbz r9, 0x93(r31)
lwz r3, 0x50(r31)
lwz r4, 0x60(r31)
lwz r5, 0x70(r31)
# r3...r5 = XYZ absolute mtx transposition
# r6...r7 = XYZ records from previous check

_update:
andi. r0, r30, 1
beq- _draw
# if saved r4 argument is false, then don't update the records in JObj padding

  stswi r3, r10, 0xC
  # else, update XYZ positions for next delta check
  # memory in registers are used for this frame's check

_draw:
or r28, r9, r28
# color gets an alpha value that reflects the time it has not been moving.
# a byte is stored alongside each record of XYZ to be ORed into given color.

rlwinm r5, r30, 8, 8, 15
li r4, 5
# line primitive params

cmpw cr6, r3, r6   # X vs X
cmpw cr7, r4, r7   # Y vs Y
crand cr6, cr7, cr6
cmpw cr7, r5, r8   # Z vs Z
crand cr6, cr7, cr6
cmpwi cr7, r9, 0xFF # has the alpha been dropping?
mfcr r29

# cr0 = comparison of r4 argument
# cr6 = comparison of XYZ delta
# cr7 = comparison of alpha to (almost) full
# r29 holds saved comparisons
beq cr0, _end_update

  beq cr6, _alpha_decr

    _alpha_incr:
    addi r11, r9, 0x40  # 4 frames to go from 0% -> 100%
    cmpwi r11, 0xFF
    ble- _alpha_store

      _cap_FF:
      li r11, 0xFF
      b _alpha_store

    _alpha_decr:
    addi r11, r9, -2    # 128 frames to go from 100% -> 0%
    cmpwi r11, 0
    bge- _alpha_store

      _cap_00:
      li r11, 0

    _alpha_store:
    stb r11, 0x93(r31)
_end_update:

li r3, 2
beq+ cr7, _setup
# if alpha is almost full, draw line regardless of delta check result
# we want to only resort to drawing disappearing dots for bones that are holding still
# this small frame buffer helps prevent an erroneous trigger of dots on hitlag

  bne+ cr6, _setup
  # else, if alpha is not full, we check the delta comparison

  # if XYZ delta == 0; then draw dot
  # if XYZ delta != 0; then draw line

    _dot_setup:
    li  r3, 1
    rlwinm r5, r30, 8, 8, 15
    addis r5, r5, 0x18
    li r4, 7
    # dot primitives params take place of line primitives
    # dot radius is half a native pixel larger than input width

_setup:
or r4, r5, r4
ori r4, r4, 0x0300  # default primitive settings, but ignores the Z buffer
# with this setting, lines will be drawn on top of anything they come after in the draw order

li r5, 0x1455 # default primitive settings for params string 2
bl <prim_main_parametric>

# r3 now = GX FIFO hardware address
# all information stored to this address goes into the drawing pipe
# all information is stored to the same address 0(r3)

_draw_child:
lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r28, 0(r3)
# vertex stored

mtcr r29
beq- cr7, _draw_parent
beq- cr6, _end_draw
# check saved delta; if 0, then draw only a point at the child vertex
# else, draw a line between parent <- child

_draw_parent:
lwz r31, 0xC(r31)
# load JObj parent

lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r28, 0(r3)
# second vertex stored

_end_draw:
bl <prim_close>

_return:
lmw r28, 0x8(sp)
addi sp, sp, 0x30
lwz  r0, 0x4(sp)
mtlr r0
blr

This last code (for now) is just a fancy version of the previous demonstration. It uses some padding in JObj structures to store X Y and Z translation values of the last measurement, so it can create a delta check each time it draws a frame.


This translation delta is checked against 0 to determine whether to draw a line OR a point, but not both. When a joint’s position in space is different from the last frame, it will be drawn as a line colored by the player slot. Sub-character players are drawn with a hue mixed with black and gray to make a desaturated color. For player slots that are higher than the controller port maximum are drawn with a base hue of white.

When a joint stops translating for more than a frame, its drawing turns into a point, and its alpha slowly decreases over ~ 2 seconds worth of action frame data.

Since it only records translation coords for the delta check, it isn’t perfect -- but this cuts away most of the unanimated joints from the display, causing a visualization that (for the most part) only illustrates actively animated joints.

This code uses calls to <prim_main_parametric>, which may be used to specify all of the available drawing parameters as immediates. r4 and r5 contain 8 bytes (6, really) that describe the following parameters:

Code:
#    r4        r5
# C0000000  00000000 = Cull Mode
#  +8 = cull backface
#  +4 = cull frontface

# 03FF0000  00000000 = Line Width/Point Size
#   1/16 pixel increments; 0x2A8 maximum width

# 00002000  00000000 = Compare Location
#  +1 = z buffer compares before texturing

# 00001000  00000000 = Z Buffer Compare
#  +1 = z buffer enables compare

# 00000800  00000000 = Z Buffer Update
#  +1 = z buffer enables update

# 00000700  00000000 = Z Buffer Comparison Logic
#  +4 = greater than
#  +2 = equal to
#  +1 = less than

# 000000FF  00000000 = Primitive Type
#   0 = quads
#   1 = --
#   2 = triangles
#   3 = trianglestrip
#   4 = trianglefan
#   5 = lines
#   6 = linestrip
#   7 = points

# 00000000  00003000 = Blend Type
#   0 = None  -- write input directly to EFB
#   1 = Blend -- blend using blending equation
#   2 = Logic -- blend using bitwise operation
#   3 = Subtract -- input subtracts from existing pixel

# 00000000  00000700 = Blend Source
#   0 = zero -- 0.0
#   1 = one  -- 1.0
#   2 = source color
#   3 = inverted source color
#   4 = source alpha
#   5 = inverted source alpha
#   6 = destination alpha
#   7 = inverted destination alpha

# 00000000  00000070 = Blend Dest
#   (see above)

# 00000000  0000000F = Blend Logic
#   0 -- CLEAR;   dst = 0
#   1 -- AND;     dst = src & dst
#   2 -- REVAND;  dst = src & ~dst
#   3 -- COPY;    dst = src
#   4 -- INVAND;  dst = ~src & dst
#   5 -- NOOP;    dst = dst
#   6 -- XOR;     dst = src ^ dst
#   7 -- OR;      dst = src | dst
#   8 -- NOR;     dst = ~(src | dst)
#   9 -- EQUIV;   dst = ~(src ^ dst)
#   A -- INV;     dst = ~dst
#   B -- REVOR;   dst = src | ~dst
#   C -- INVCOPY; dst = ~src
#   D -- INVOR;   dst = ~src | dst
#   E -- NAND;    dst = ~(src & dst)
#   F -- SET;     dst = 1
---

I’ll have more examples soon. In the meantime, feel free to make your own.
@UnclePunch T tauKhan @rmn
 
Last edited:

tauKhan

Smash Lord
Joined
Feb 9, 2014
Messages
1,333
#3
Oh wow, this is just the library I wanted :) . Amazing stuff, will definitively put this into use.

Also almost did a code similar to ECB Anchor earlier. Sidenote: I'd remove the first ECB node (0x7F8) from the anchor draw. It just points to TopN and isn't used for determining ECB vertices. In fact it looks like it's probably not read anywhere. Having the TopN in the anchor also distracts from the fact that some characters have TopN as another node, actually used for calculation.
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
250
#5
Oh wow, this is just the library I wanted :) . Amazing stuff, will definitively put this into use.
I’m glad to hear that! I have some other libraries that I’ll be releasing soon that might also interest you. They should make it easier to create draw events that can be shared by multiple codes, as well as store vertex information in a buffer that persists beyond the time it was drawn.

Also almost did a code similar to ECB Anchor earlier.
Don’t let me step on your toes! These demos are barely a step beyond “hello world” scripts meant to test the library and explore some structures.

I’ve only ever looked into how ecb quads were drawn; you seem to be more acquainted with how they’re updated. I was extremely impressed with your ECB wall collision raycast code. If you can use this library to make a demonstrative visualization of how the ECB anchor joints influence the ECB shape, I’ll be sure to add it to the example codes list.

The same goes for anyone else interested in this library; if you would like your codes listed in the example codes list -- tag me in your post. I’ll add a link to it in the OP, appending newest codes to the top of the index.

I'd remove the first ECB node (0x7F8) from the anchor draw. It just points to TopN and isn't used for determining ECB vertices. In fact it looks like it's probably not read anywhere. Having the TopN in the anchor also distracts from the fact that some characters have TopN as another node, actually used for calculation.
I agree. I excluded TopN, TransN, XRotN, and YRotN from the player skeleton codes because they were distracting, so it makes sense to do the same here. TopN is an informative joint for other reasons, but not so much in this case.

---

Here’s a second version of the code that uses thick points instead of lines, and removes TopN from the display. The points are each given a distinct hue, which I think makes a much clearer identity for the joints:


ECB Anchor Test 2

Code:
-==-

ECB Anchor Test 2
this version of the code only draws points, and skips the TopN joint
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
2C1D0002 40A20068 38600006 3C800038 60840307 38A01455
bl <prim_main_parametric>
809C002C 38000006 7C0903A6 7CC902A6 38E00001 50C7C1CE 50C77BDE 50C735EE 1C0700FF 80A407FC C0050050 D0030000 C0050060 D0030000 C0050070 D0030000 90030000 38840004 4200FFC4
bl <prim_close>
BB61008C 00000000
Code:
-==-
!
ASM - ECB Anchor Test 2
this version of the code only draws points, and skips the TopN joint
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
# this injection is in the middle of a drawing loop used by players
# it has the active camera set to the main camera, so drawing will appear on screen
# injection placement ensures it draws after the player
# r28 = player entity
# r29 = z loop iteration (out of 2)
cmpwi r29, 2
bne+ _return
# with this, our drawing will only happen after everything on the player is drawn

.macro toChannel, rD, rS, chan, mask, pad
rlwimi \rD, \rS, 32-(\chan<<3)-\pad, \mask<<(32-(\chan<<3))
.endm
.set chRd, 1
.set chGr, 2
.set chBl, 3
# macro helps us create color channels from binary

li r3, 6  # r3 argument = number of vertices to draw
lis r4, 0x38
ori r4, r4, 0x0307
li  r5, 0x1455
bl <prim_main_parametric>
# returns r3 = fifo pipe address
# writing to this will let us create vertices
# we need to write to 0(r3) for every piece of information given to GX

lwz r4, 0x2C(r28)
li r0, 6
mtctr r0
# load player data, and set up a bdnz loop for 6 iterations
# this will catch each JObj we want to trace

_loop:
mfctr r6
li r7, 1
toChannel r7, r6, chRd, 1, 0
toChannel r7, r6, chGr, 1, 1
toChannel r7, r6, chBl, 1, 2
mulli r0, r7, 0xFF
# r0 = RGBA color generated from ctr number
# 0x7FC = yellow
# 0x800 = magenta
# 0x804 = red
# 0x808 = cyan
# 0x80C = green
# 0x810 = blue

lwz  r5, 0x7FC(r4)
lfs  f0, 0x50(r5) # load X vert translation
stfs f0, 0(r3)    # write to GX FIFO
lfs  f0, 0x60(r5) # load Y
stfs f0, 0(r3)    # write Y
lfs  f0, 0x70(r5) # load Z
stfs f0, 0(r3)    # write Z
stw  r0, 0(r3)    # write RGBA color
# X, Y, Z, and color create a vertex

addi r4, r4, 4   # move to next JObj
bdnz+ _loop      # iterate loop
# loop will finish after 6 vertices have been drawn

bl <prim_close>
# finish the primitive with this

_return:
lmw    r27, 0x008C (sp)  # original instruction
.long 0

The linestrip in the previous test code was meant to illustrate the order of the bone index. You can still sort of do that in this new one because the colors are built from a CTR countdown of the number 6...1. The loop that draws the bones uses rlwimi instructions to convert the last 3 bits of an input number into a color.

Edit: updated with corrections to index order

0x7FC - cyan
0x800 - magenta
0x804 - blue
0x808 - yellow
0x80C - green
0x810 - red

---

Very cool!

This could be really useful when working on creating new animations.
Thanks. I think I’ll be using this library to create quite a few code experiments that prod at joint-related stuff, so hopefully there’s more to come.

I got caught up in some more example codes I wanted to share for a little while, but I ultimately started focusing on my other libraries because they’ll help me do some things I can’t do at the moment.

I did manage to finish this however. It’s a sort of general purpose JObj relationship tracing function for MCM. It’s a lot fancier than the other functions in my previous test codes:

<draw_JObj_relation>
Code:
<draw_JObj_relation>
7C0802A6 90010004 9421FFC0 BF410008 7F400026 3B610020 7C7B85AA BB9B0000 54C6801E 7CC10120 408E0010 807C0010 2C030000 41A0FFCD 408D0010 807C0008 2C030000 4180FFBD 408C0020 807C0080 2C030000 40A00014 80630008 2C030000 60C60006 4180FF9D 2C050000 4181005C 41820090 48000019 7C6802A6 54A5103A 38630005 7C6903A6 4E800420 4E800021 48000030 48000024 48000018 807C0080 2C030000 40A0005C 80630008 48000020 807C0010 48000018 807C0008 48000010 807C000C 48000008 7CA32B78 2C030000 40800030 7C7E1B78 57E4609E 60840305 38A01455 38600002
bl <prim_main_parametric>
7FC3F378 7FA4EB78
bl <draw_JObj_as_vertex>
7F83E378
bl <draw_JObj_as_vertex>
408F0024 57E4609E 60840307 38A01455 38600001
bl <prim_main_parametric>
7F83E378 7FA4EB78
bl <draw_JObj_as_vertex>
7C7B84AA 7F4FF120 BB410008 38210040 80010004 7C0803A6 4E800020

<draw_JObj_as_vertex>
3CA0CC01 C0030050 D0058000 C0030060 D0058000 C0030070 D0058000 90858000 4E800020
Code:
<draw_JObj_relation>
# r3 = THIS JObj
# r4 = color
# r5 = relation type
#      0 - draw no line
#      1 - draw line to parent
#      2 - draw line to sibling
#      3 - draw line to child
#      4 - draw line to RObj ref
#      address = draw line to second JObj (address)
# r6 = draw bools and line width
#      +1 = draw point on self
#      +2 = draw for each child
#      +4 = draw for each sibling
#      +8 = draw attached RObj families
#      0xFFF0 - mask for draw width of lines/points
_recursion:
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x40(sp)
stmw r26, 0x8(sp)
mfcr r26 # cr is backed up and restored before returning
addi r27, sp, 0x20
stswi r3, r27, 0x10
lmw   r28, 0(r27)
slwi  r6, r6, 16
mtcrf 0b00010000, r6
# r28...r31 = r3...r6
# r28 = THIS JObj
# r29 = color
# r30 = relation type
# r31 = line width
# cr4 holds saved bools
# since we've backed up cr, this will persist through recursive calls

.set bSelf,    15 # draw point for self
.set bChild,   14 # recursion options
.set bSibling, 13
.set bRObj,    12
# these bits can be checked from cr4

_check_child:
bf bChild, _check_sibling
  lwz r3, 0x10(r28)
  cmpwi r3, 0
  bltl- _recursion
  # call self recursively for each child, if draw option specifies

_check_sibling:
bf bSibling, _check_robj
  lwz r3, 0x8(r28)
  cmpwi r3, 0
  bltl+ _recursion
  # call self recursively for each sibling, if draw option specifies

_check_robj:
bf bRObj, _end_of_recursion_checks
  lwz r3, 0x80(r28)
  cmpwi r3, 0
  bge+ _end_of_recursion_checks
    lwz r3, 0x8(r3)
    cmpwi r3, 0
    ori r6, r6, 0x6  # ensure RObj family is drawn entirely
    bltl+ _recursion

_end_of_recursion_checks:
cmpwi r5, 0
bgt- _address_line
beq- _draw_point
bl _get_branch_index
mflr r3
slwi r5, r5, 2
addi r3, r3, r5
mtctr r3
bctr
# bctr puts PC at branch according to r5 option

_get_branch_index:
blrl
b _parent
b _sibling
b _child

_RObj:
lwz r3, 0x80(r28)
cmpwi r3, 0
bge+ _draw_point
lwz r3, 0x8(r3)
b _draw_line
# RObj relation only drawn if RObj exists

_child:
lwz r3, 0x10(r28)
b _draw_line

_sibling:
lwz r3, 0x8(r28)
b _draw_line

_parent:
lwz r3, 0xC(r28)
b _draw_line

_address_line:
mr r3, r5

_draw_line:
# r3 = joint to draw line to from THIS JObj
# r3 may possibly be null, so we check
cmpwi r3, 0
bge _draw_point
# skip line drawing if no second joint to draw to

mr r30, r3
rlwinm r4, r31, 12, 0x3FFF0000
ori r4, r4, 0x0305
li  r5, 0x1455
li r3, 2
bl <prim_main_parametric>
mr r3, r30
mr r4, r29
bl <draw_JObj_as_vertex>
mr r3, r28
bl <draw_JObj_as_vertex>
# line is drawn

_draw_point:
bf bSelf, _return
  rlwinm r4, r31, 12, 0x3FFF0000
  ori r4, r4, 0x0307
  li  r5, 0x1455
  li r3, 1
  bl <prim_main_parametric>
  mr r3, r28
  mr r4, r29
  bl <draw_JObj_as_vertex>
  # point is drawn only if option is checked in bools

_return:
lswi r3, r27, 0x10
# each return restores r3...r6 for recursion arguments
mtcr r26
# cr is restored so that we don't have to move bits into CR more than once
lmw r26, 0x8(sp)
addi sp, sp, 0x40
lwz r0, 0x4(sp)
mtlr r0
blr



<draw_JObj_as_vertex>
lis r5, 0xCC01
lfs f0, 0x50(r3)
stfs f0, -0x8000(r5)
lfs f0, 0x60(r3)
stfs f0, -0x8000(r5)
lfs f0, 0x70(r3)
stfs f0, -0x8000(r5)
stw r4, -0x8000(r5)
blr


r3 = root JObj
  • This will act as the parent of the drawing pass.
    • Since the function is recursive, the parent is actually drawn last.
  • If specified, the root JObj may act as a single JObj with no recursive drawing.
r4 = RGBA color of this drawing pass
r5 = joint relationship
  • 0 = no relationship; just THIS joint
  • 1 = LINE between THIS and PARENT
  • 2 = LINE between THIS and SIBLING
  • 3 = LINE between THIS and CHILD
  • 4 = LINE between THIS and ROBJ link
  • else = interpret r5 as a second JObj address
    • create LINE between THIS and argument JOBJ
r6 = line width and skeleton options, encoded as integers and bools in an hword:
  • 0x1F00 = line width, in native pixels
  • 0x00F0 = line width, in 1/16 pixels
  • 0x0001 = include point at location of THIS joint
  • 0x0002 = draw for each sibling in skeleton
  • 0x0004 = draw for each child in skeleton
  • 0x0008 = draw for each RObj link in skeleton
The function has only been lightly tested.

I’m curious if this could be used to draw out some unknown entity skeletons in the various global entity pointer tables that exist in r13. The offset 0x28 in each entity type is like some kind of “root hsd object” pointer, or something. It’s usually a root JObj, but I’ve also seen them point to CObjs in certain entity types. The HSD info table at the start of the object can be used to identify JObj class types, so it’d probably just be a matter of testing for that.
 
Last edited:
Top