2019-08-25 04:46:40 +00:00
|
|
|
#include <ultra64.h>
|
|
|
|
|
|
|
|
#include "sm64.h"
|
|
|
|
#include "game.h"
|
|
|
|
#include "mario.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "save_file.h"
|
|
|
|
#include "engine/surface_collision.h"
|
|
|
|
#include "engine/graph_node.h"
|
|
|
|
#include "geo_misc.h"
|
|
|
|
#include "area.h"
|
2019-10-05 19:08:05 +00:00
|
|
|
#include "segment2.h"
|
2019-08-25 04:46:40 +00:00
|
|
|
#include "paintings.h"
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @file paintings.c
|
|
|
|
*
|
|
|
|
* Implements the rippling painting effect. Paintings are GraphNodes that exist without being connected
|
|
|
|
* to any particular object.
|
|
|
|
*
|
|
|
|
* Paintings are defined in level data. Look at levels/castle_inside/painting.inc.c for examples.
|
|
|
|
*
|
|
|
|
* The ripple effect uses data that is split into several parts:
|
|
|
|
* The mesh positions are generated from a base mesh. See seg2_painting_triangle_mesh near the
|
|
|
|
* bottom of bin/segment2.c
|
|
|
|
*
|
|
|
|
* The lighting for the ripple is also generated from a base table, seg2_painting_mesh_neighbor_tris
|
|
|
|
* in bin/segment2.c
|
|
|
|
*
|
|
|
|
* Each painting's texture uses yet another table to map its texture to the mesh.
|
|
|
|
* These maps are in level data, see levels/castle_inside/painting.inc.c for example.
|
|
|
|
*
|
|
|
|
* Finally, each painting has two display lists, normal and rippling, which are defined in the same
|
|
|
|
* level data file as the Painting itself. See levels/castle_inside/painting.inc.c.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Painting state machine:
|
|
|
|
* Paintings spawn in the PAINTING_IDLE state
|
|
|
|
* From IDLE, paintings can change to PAINTING_RIPPLE or PAINTING_ENTERED
|
|
|
|
* - This state checks for ENTERED because if mario waits long enough, a PROXIMITY painting could
|
|
|
|
* reset to IDLE
|
|
|
|
*
|
|
|
|
* Paintings in the PAINTING_RIPPLE state are passively rippling.
|
|
|
|
* For RIPPLE_TRIGGER_PROXIMITY paintings, this means mario bumped the wall in front of the
|
|
|
|
* painting.
|
|
|
|
*
|
|
|
|
* Paintings that use RIPPLE_TRIGGER_CONTINUOUS try to transition to this state as soon as possible,
|
|
|
|
* usually when mario enters the room.
|
|
|
|
*
|
|
|
|
* A PROXIMITY painting will automatically reset to IDLE if its ripple magnitude becomes small
|
|
|
|
* enough.
|
|
|
|
*
|
|
|
|
* Paintings in the PAINTING_ENTERED state have been entered by mario.
|
|
|
|
* A CONTINUOUS painting will automatically reset to RIPPLE if its ripple magnitude becomes small
|
|
|
|
* enough.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers a passive ripple on the left side of the painting.
|
|
|
|
*/
|
|
|
|
#define RIPPLE_LEFT 0x20
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers a passive ripple in the middle the painting.
|
|
|
|
*/
|
|
|
|
#define RIPPLE_MIDDLE 0x10
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers a passive ripple on the right side of the painting.
|
|
|
|
*/
|
|
|
|
#define RIPPLE_RIGHT 0x8
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers an entry ripple on the left side of the painting.
|
|
|
|
*/
|
|
|
|
#define ENTER_LEFT 0x4
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers an entry ripple in the middle of the painting.
|
|
|
|
*/
|
|
|
|
#define ENTER_MIDDLE 0x2
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers an entry ripple on the right side of the painting.
|
|
|
|
*/
|
|
|
|
#define ENTER_RIGHT 0x1
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use the 1/4th part of the painting that is nearest to mario's current floor.
|
|
|
|
*/
|
|
|
|
#define NEAREST_4TH 30
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use mario's relative x position.
|
|
|
|
* @see painting_mario_x
|
|
|
|
*/
|
|
|
|
#define MARIO_X 40
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use the x center of the painting.
|
|
|
|
*/
|
|
|
|
#define MIDDLE_X 50
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use mario's relative y position.
|
|
|
|
* @see painting_mario_y
|
|
|
|
*/
|
|
|
|
#define MARIO_Y 60
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use mario's relative z position.
|
|
|
|
* @see painting_mario_z
|
|
|
|
*/
|
|
|
|
#define MARIO_Z 70
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use the y center of the painting.
|
|
|
|
*/
|
|
|
|
#define MIDDLE_Y 80
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Does nothing to the timer.
|
|
|
|
* Why -56 instead of false? Who knows.
|
|
|
|
*/
|
|
|
|
#define DONT_RESET -56
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset the timer to 0.
|
|
|
|
*/
|
|
|
|
#define RESET_TIMER 100
|
|
|
|
|
|
|
|
/// A copy of the type of floor mario is standing on.
|
2019-08-25 04:46:40 +00:00
|
|
|
s16 gPaintingMarioFloorType;
|
2020-03-02 03:42:52 +00:00
|
|
|
// A copy of mario's position
|
|
|
|
f32 gPaintingMarioXPos, gPaintingMarioYPos, gPaintingMarioZPos;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* When a painting is rippling, this mesh is generated each frame using the Painting's parameters.
|
|
|
|
*
|
|
|
|
* This mesh only contains the vertex positions and normals.
|
|
|
|
* Paintings use an additional array to map textures to the mesh.
|
|
|
|
*/
|
|
|
|
struct PaintingMeshVertex *gPaintingMesh;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The painting's surface normals, used to approximate each of the vertex normals (for gouraud shading).
|
|
|
|
*/
|
|
|
|
Vec3f *gPaintingTriNorms;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The painting that is currently rippling. Only one painting can be rippling at once.
|
|
|
|
*/
|
|
|
|
struct Painting *gRipplingPainting;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether the DDD painting is moved forward, should being moving backwards, or has already moved backwards.
|
|
|
|
*/
|
|
|
|
s8 gDddPaintingStatus;
|
|
|
|
|
|
|
|
struct Painting *sHmcPaintings[] = {
|
2019-11-03 19:36:27 +00:00
|
|
|
&cotmc_painting,
|
2019-08-25 04:46:40 +00:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
struct Painting *sInsideCastlePaintings[] = {
|
2019-11-03 19:36:27 +00:00
|
|
|
&bob_painting, &ccm_painting, &wf_painting, &jrb_painting, &lll_painting,
|
|
|
|
&ssl_painting, &hmc_painting, &ddd_painting, &wdw_painting, &thi_tiny_painting,
|
|
|
|
&ttm_painting, &ttc_painting, &sl_painting, &thi_huge_painting, NULL,
|
2019-08-25 04:46:40 +00:00
|
|
|
};
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
struct Painting *sTtmPaintings[] = {
|
2019-11-03 19:36:27 +00:00
|
|
|
&ttm_slide_painting,
|
2019-08-25 04:46:40 +00:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
struct Painting **sPaintingGroups[] = {
|
|
|
|
sHmcPaintings,
|
|
|
|
sInsideCastlePaintings,
|
|
|
|
sTtmPaintings,
|
2019-08-25 04:46:40 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
s16 gPaintingUpdateCounter = 1;
|
|
|
|
s16 gLastPaintingUpdateCounter = 0;
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Stop paintings in paintingGroup from rippling if their id is different from *idptr.
|
|
|
|
*/
|
|
|
|
void stop_other_paintings(s16 *idptr, struct Painting *paintingGroup[]) {
|
2019-08-25 04:46:40 +00:00
|
|
|
s16 index;
|
|
|
|
s16 id = *idptr;
|
|
|
|
|
|
|
|
index = 0;
|
2020-03-02 03:42:52 +00:00
|
|
|
while (paintingGroup[index] != NULL) {
|
2019-11-03 19:36:27 +00:00
|
|
|
struct Painting *painting = segmented_to_virtual(paintingGroup[index]);
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// stop all rippling except for the selected painting
|
2019-09-01 19:50:50 +00:00
|
|
|
if (painting->id != id) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting->state = 0;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @return mario's y position inside the painting (bounded).
|
|
|
|
*/
|
|
|
|
f32 painting_mario_y(struct Painting *painting) {
|
|
|
|
//! Unnecessary use of double constants
|
|
|
|
// Add 50 to make the ripple closer to mario's center of mass.
|
|
|
|
f32 relY = gPaintingMarioYPos - painting->posY + 50.0;
|
|
|
|
|
|
|
|
if (relY < 0.0) {
|
|
|
|
relY = 0.0;
|
|
|
|
} else if (relY > painting->size) {
|
|
|
|
relY = painting->size;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
return relY;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @return mario's z position inside the painting (bounded).
|
|
|
|
*/
|
|
|
|
f32 painting_mario_z(struct Painting *painting) {
|
|
|
|
f32 relZ = painting->posZ - gPaintingMarioZPos;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (relZ < 0.0) {
|
|
|
|
relZ = 0.0;
|
|
|
|
} else if (relZ > painting->size) {
|
|
|
|
relZ = painting->size;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
return relZ;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @return The y origin for the ripple, based on ySource.
|
|
|
|
* For floor paintings, the z-axis is treated as y.
|
|
|
|
*/
|
|
|
|
f32 painting_ripple_y(struct Painting *painting, s8 ySource) {
|
|
|
|
switch (ySource) {
|
2019-08-25 04:46:40 +00:00
|
|
|
case MARIO_Y:
|
2020-03-02 03:42:52 +00:00
|
|
|
return painting_mario_y(painting); // normal wall paintings
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
case MARIO_Z:
|
2020-03-02 03:42:52 +00:00
|
|
|
return painting_mario_z(painting); // floor paintings use X and Z
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
case MIDDLE_Y:
|
2020-03-02 03:42:52 +00:00
|
|
|
return painting->size / 2.0; // some concentric ripples don't care about Mario
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Return the quarter of the painting that is closest to the floor mario entered.
|
|
|
|
*/
|
|
|
|
f32 painting_nearest_4th(struct Painting *painting) {
|
|
|
|
f32 firstQuarter = painting->size / 4.0; // 1/4 of the way across the painting
|
|
|
|
f32 secondQuarter = painting->size / 2.0; // 1/2 of the way across the painting
|
|
|
|
f32 thirdQuarter = painting->size * 3.0 / 4.0; // 3/4 of the way across the painting
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (painting->floorEntered & RIPPLE_LEFT) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return firstQuarter;
|
2020-03-02 03:42:52 +00:00
|
|
|
} else if (painting->floorEntered & RIPPLE_MIDDLE) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return secondQuarter;
|
2020-03-02 03:42:52 +00:00
|
|
|
} else if (painting->floorEntered & RIPPLE_RIGHT) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return thirdQuarter;
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// Same as ripple floors.
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_LEFT) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return firstQuarter;
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_MIDDLE) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return secondQuarter;
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_RIGHT) {
|
2019-08-25 04:46:40 +00:00
|
|
|
return thirdQuarter;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @return mario's x position inside the painting (bounded).
|
|
|
|
*/
|
|
|
|
f32 painting_mario_x(struct Painting *painting) {
|
|
|
|
f32 relX = gPaintingMarioXPos - painting->posX;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (relX < 0.0) {
|
|
|
|
relX = 0.0;
|
|
|
|
} else if (relX > painting->size) {
|
|
|
|
relX = painting->size;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
return relX;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @return The x origin for the ripple, based on xSource.
|
|
|
|
*/
|
|
|
|
f32 painting_ripple_x(struct Painting *painting, s8 xSource) {
|
|
|
|
switch (xSource) {
|
|
|
|
case NEAREST_4TH: // normal wall paintings
|
|
|
|
return painting_nearest_4th(painting);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
case MARIO_X: // horizontally placed paintings use X and Z
|
2020-03-02 03:42:52 +00:00
|
|
|
return painting_mario_x(painting);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
case MIDDLE_X: // concentric rippling may not care about Mario
|
2020-03-02 03:42:52 +00:00
|
|
|
return painting->size / 2.0;
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Set the painting's state, causing it to start a passive ripple or a ripple from mario entering.
|
|
|
|
*
|
|
|
|
* @param state The state to enter
|
|
|
|
* @param painting,paintingGroup identifies the painting that is changing state
|
|
|
|
* @param xSource,ySource what to use for the x and y origin of the ripple
|
|
|
|
* @param resetTimer if 100, set the timer to 0
|
|
|
|
*/
|
|
|
|
void painting_state(s8 state, struct Painting *painting, struct Painting *paintingGroup[],
|
|
|
|
s8 xSource, s8 ySource, s8 resetTimer) {
|
|
|
|
// make sure no other paintings are rippling
|
|
|
|
stop_other_paintings(&painting->id, paintingGroup);
|
|
|
|
|
|
|
|
// use a different set of variables depending on the state
|
|
|
|
switch (state) {
|
|
|
|
case PAINTING_RIPPLE:
|
|
|
|
painting->currRippleMag = painting->passiveRippleMag;
|
|
|
|
painting->rippleDecay = painting->passiveRippleDecay;
|
|
|
|
painting->currRippleRate = painting->passiveRippleRate;
|
2019-08-25 04:46:40 +00:00
|
|
|
painting->dispersionFactor = painting->passiveDispersionFactor;
|
|
|
|
break;
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
case PAINTING_ENTERED:
|
|
|
|
painting->currRippleMag = painting->entryRippleMag;
|
|
|
|
painting->rippleDecay = painting->entryRippleDecay;
|
|
|
|
painting->currRippleRate = painting->entryRippleRate;
|
2019-08-25 04:46:40 +00:00
|
|
|
painting->dispersionFactor = painting->entryDispersionFactor;
|
|
|
|
break;
|
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
painting->state = state;
|
|
|
|
painting->rippleX = painting_ripple_x(painting, xSource);
|
|
|
|
painting->rippleY = painting_ripple_y(painting, ySource);
|
2019-08-25 04:46:40 +00:00
|
|
|
gPaintingMarioYEntry = gPaintingMarioYPos;
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// Because true or false would be too simple...
|
2019-09-01 19:50:50 +00:00
|
|
|
if (resetTimer == RESET_TIMER) {
|
2019-08-25 04:46:40 +00:00
|
|
|
painting->rippleTimer = 0.0f;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
gRipplingPainting = painting;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Idle update function for wall paintings that use RIPPLE_TRIGGER_PROXIMITY.
|
|
|
|
*/
|
|
|
|
void wall_painting_proximity_idle(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
// Check for mario triggering a ripple
|
|
|
|
if (painting->floorEntered & RIPPLE_LEFT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_MIDDLE) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_RIGHT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
|
|
|
|
|
|
|
// Check for mario entering
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Rippling update function for wall paintings that use RIPPLE_TRIGGER_PROXIMITY.
|
|
|
|
*/
|
|
|
|
void wall_painting_proximity_rippling(struct Painting *painting, struct Painting *paintingGroup[]) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if (painting->floorEntered & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Idle update function for wall paintings that use RIPPLE_TRIGGER_CONTINUOUS.
|
|
|
|
*/
|
|
|
|
void wall_painting_continuous_idle(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
// Check for mario triggering a ripple
|
|
|
|
if (painting->floorEntered & RIPPLE_LEFT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_MIDDLE) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_RIGHT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y, RESET_TIMER);
|
|
|
|
|
|
|
|
// Check for mario entering
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Rippling update function for wall paintings that use RIPPLE_TRIGGER_CONTINUOUS.
|
|
|
|
*/
|
|
|
|
void wall_painting_continuous_rippling(struct Painting *painting, struct Painting *paintingGroup[]) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if (painting->floorEntered & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, DONT_RESET);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, DONT_RESET);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->floorEntered & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, NEAREST_4TH, MARIO_Y, DONT_RESET);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Idle update function for floor paintings that use RIPPLE_TRIGGER_PROXIMITY.
|
|
|
|
*
|
|
|
|
* No floor paintings use RIPPLE_TRIGGER_PROXIMITY in the game.
|
|
|
|
*/
|
|
|
|
void floor_painting_proximity_idle(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
// Check for mario triggering a ripple
|
|
|
|
if (painting->floorEntered & RIPPLE_LEFT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_MIDDLE) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_RIGHT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
|
|
|
|
|
|
|
// Only check for mario entering if he jumped below the surface
|
|
|
|
} else if (painting->marioWentUnder) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if (painting->currFloor & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Rippling update function for floor paintings that use RIPPLE_TRIGGER_PROXIMITY.
|
|
|
|
*
|
|
|
|
* No floor paintings use RIPPLE_TRIGGER_PROXIMITY in the game.
|
|
|
|
*/
|
|
|
|
void floor_painting_proximity_rippling(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
if (painting->marioWentUnder) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if (painting->currFloor & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Idle update function for floor paintings that use RIPPLE_TRIGGER_CONTINUOUS.
|
|
|
|
*
|
|
|
|
* Both floor paintings (HMC and CotMC) are hidden behind a door, which hides the ripple's start up.
|
|
|
|
* The floor just inside the doorway is RIPPLE_LEFT, so the painting starts rippling as soon as mario
|
|
|
|
* enters the room.
|
|
|
|
*/
|
|
|
|
void floor_painting_continuous_idle(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
// Check for mario triggering a ripple
|
|
|
|
if (painting->floorEntered & RIPPLE_LEFT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_MIDDLE) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y, RESET_TIMER);
|
|
|
|
} else if (painting->floorEntered & RIPPLE_RIGHT) {
|
|
|
|
painting_state(PAINTING_RIPPLE, painting, paintingGroup, MIDDLE_X, MIDDLE_Y, RESET_TIMER);
|
|
|
|
|
|
|
|
// Check for mario entering
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, RESET_TIMER);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Rippling update function for floor paintings that use RIPPLE_TRIGGER_CONTINUOUS.
|
|
|
|
*/
|
|
|
|
void floor_painting_continuous_rippling(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
if (painting->marioWentUnder) {
|
2019-09-01 19:50:50 +00:00
|
|
|
if (painting->currFloor & ENTER_LEFT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, DONT_RESET);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_MIDDLE) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, DONT_RESET);
|
2019-09-01 19:50:50 +00:00
|
|
|
} else if (painting->currFloor & ENTER_RIGHT) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting_state(PAINTING_ENTERED, painting, paintingGroup, MARIO_X, MARIO_Z, DONT_RESET);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Check for mario entering one of the special floors associated with the painting.
|
|
|
|
*/
|
2019-11-03 19:36:27 +00:00
|
|
|
void painting_update_floors(struct Painting *painting) {
|
2019-08-25 04:46:40 +00:00
|
|
|
s16 paintingId = painting->id;
|
2020-03-02 03:42:52 +00:00
|
|
|
s8 rippleLeft = 0;
|
|
|
|
s8 rippleMiddle = 0;
|
|
|
|
s8 rippleRight = 0;
|
|
|
|
s8 enterLeft = 0;
|
|
|
|
s8 enterMiddle = 0;
|
|
|
|
s8 enterRight = 0;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
/* The area in front of every painting in the game (except HMC and CotMC, which *\
|
|
|
|
|* act a little differently) is made up of 3 special floor triangles with special *|
|
|
|
|
|* (unique) surface types. This code checks which surface Mario is currently on *|
|
|
|
|
\* and sets a bitfield accordingly. */
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
// check if Mario's current floor is one of the special floors
|
|
|
|
if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WOBBLE_A6) {
|
|
|
|
rippleLeft = RIPPLE_LEFT;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
|
|
|
if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WOBBLE_A7) {
|
2020-03-02 03:42:52 +00:00
|
|
|
rippleMiddle = RIPPLE_MIDDLE;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
|
|
|
if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WOBBLE_A8) {
|
2020-03-02 03:42:52 +00:00
|
|
|
rippleRight = RIPPLE_RIGHT;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
|
|
|
if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WARP_D3) {
|
2020-03-02 03:42:52 +00:00
|
|
|
enterLeft = ENTER_LEFT;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
|
|
|
if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WARP_D4) {
|
2020-03-02 03:42:52 +00:00
|
|
|
enterMiddle = ENTER_MIDDLE;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
|
|
|
if (gPaintingMarioFloorType == paintingId * 3 + SURFACE_PAINTING_WARP_D5) {
|
2020-03-02 03:42:52 +00:00
|
|
|
enterRight = ENTER_RIGHT;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
painting->lastFloor = painting->currFloor;
|
2020-03-02 03:42:52 +00:00
|
|
|
// at most 1 of these will be nonzero;
|
|
|
|
painting->currFloor = rippleLeft + rippleMiddle + rippleRight + enterLeft + enterMiddle + enterRight;
|
|
|
|
|
|
|
|
// floorEntered is true iff currFloor is true and lastFloor is false
|
|
|
|
// (Mario just entered the floor on this frame)
|
|
|
|
painting->floorEntered = (painting->lastFloor ^ painting->currFloor) & painting->currFloor;
|
|
|
|
|
|
|
|
painting->marioWasUnder = painting->marioIsUnder;
|
|
|
|
// Check if mario has fallen below the painting (used for floor paintings)
|
|
|
|
if (gPaintingMarioYPos < painting->posY) {
|
|
|
|
painting->marioIsUnder = TRUE;
|
2019-09-01 19:50:50 +00:00
|
|
|
} else {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting->marioIsUnder = FALSE;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// mario "went under" if he was not under last frame, but is under now
|
|
|
|
painting->marioWentUnder = (painting->marioWasUnder ^ painting->marioIsUnder) & painting->marioIsUnder;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Update the ripple's timer and magnitude, making it propagate outwards.
|
|
|
|
*
|
|
|
|
* Automatically changes the painting back to IDLE state (or RIPPLE for continuous paintings) if the
|
|
|
|
* ripple's magnitude becomes small enough.
|
|
|
|
*/
|
|
|
|
void painting_update_ripple_state(struct Painting *painting) {
|
2019-08-25 04:46:40 +00:00
|
|
|
if (gPaintingUpdateCounter != gLastPaintingUpdateCounter) {
|
2020-03-02 03:42:52 +00:00
|
|
|
painting->currRippleMag *= painting->rippleDecay;
|
|
|
|
|
|
|
|
//! After ~6.47 days, paintings with RIPPLE_TRIGGER_CONTINUOUS will increment this to
|
|
|
|
//! 16777216 (1 << 24), at which point it will freeze (due to floating-point
|
|
|
|
//! imprecision?) and the painting will stop rippling. This happens to HMC, DDD, and
|
|
|
|
//! CotMC. This happens on Wii VC. Untested on N64 and Wii U VC.
|
|
|
|
painting->rippleTimer += 1.0;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
if (painting->rippleTrigger == RIPPLE_TRIGGER_PROXIMITY) {
|
2020-03-02 03:42:52 +00:00
|
|
|
// if the painting is barely rippling, make it stop rippling
|
|
|
|
if (painting->currRippleMag <= 1.0) {
|
|
|
|
painting->state = PAINTING_IDLE;
|
|
|
|
gRipplingPainting = NULL;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
} else if (painting->rippleTrigger == RIPPLE_TRIGGER_CONTINUOUS) {
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
// if the painting is doing the entry ripple but the ripples are as small as those from the
|
|
|
|
// passive ripple, make it do a passive ripple
|
|
|
|
// If mario goes below the surface but doesn't warp, the painting will eventually reset.
|
|
|
|
if (painting->state == PAINTING_ENTERED && painting->currRippleMag <= painting->passiveRippleMag) {
|
|
|
|
|
|
|
|
painting->state = PAINTING_RIPPLE;
|
2019-08-25 04:46:40 +00:00
|
|
|
painting->currRippleMag = painting->passiveRippleMag;
|
2020-03-02 03:42:52 +00:00
|
|
|
painting->rippleDecay = painting->passiveRippleDecay;
|
2019-08-25 04:46:40 +00:00
|
|
|
painting->currRippleRate = painting->passiveRippleRate;
|
|
|
|
painting->dispersionFactor = painting->passiveDispersionFactor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* @return the ripple function at posX, posY
|
|
|
|
* note that posX and posY correspond to a point on the face of the painting, not actual axes
|
|
|
|
*/
|
|
|
|
s16 calculate_ripple_at_point(struct Painting *painting, f32 posX, f32 posY) {
|
|
|
|
/// Controls the peaks of the ripple.
|
|
|
|
f32 rippleMag = painting->currRippleMag;
|
|
|
|
/// Controls the ripple's frequency
|
|
|
|
f32 rippleRate = painting->currRippleRate;
|
|
|
|
/// Controls how fast the ripple spreads
|
|
|
|
f32 dispersionFactor = painting->dispersionFactor;
|
|
|
|
/// How far the ripple has spread
|
|
|
|
f32 rippleTimer = painting->rippleTimer;
|
|
|
|
/// x and y ripple origin
|
|
|
|
f32 rippleX = painting->rippleX;
|
|
|
|
f32 rippleY = painting->rippleY;
|
|
|
|
|
|
|
|
f32 distanceToOrigin;
|
|
|
|
f32 rippleDistance;
|
|
|
|
|
|
|
|
posX *= painting->size / PAINTING_SIZE;
|
|
|
|
posY *= painting->size / PAINTING_SIZE;
|
|
|
|
distanceToOrigin = sqrtf((posX - rippleX) * (posX - rippleX) + (posY - rippleY) * (posY - rippleY));
|
|
|
|
// A larger dispersionFactor makes the ripple spread slower
|
|
|
|
rippleDistance = distanceToOrigin / dispersionFactor;
|
|
|
|
if (rippleTimer < rippleDistance) {
|
|
|
|
// if the ripple hasn't reached the point yet, make the point magnitude 0
|
|
|
|
return 0;
|
2019-08-25 04:46:40 +00:00
|
|
|
} else {
|
2020-03-02 03:42:52 +00:00
|
|
|
// use a cosine wave to make the ripple go up and down,
|
|
|
|
// scaled by the painting's ripple magnitude
|
|
|
|
f32 rippleZ = rippleMag * cosf(rippleRate * (2 * M_PI) * (rippleTimer - rippleDistance));
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
// round it to an int and return it
|
|
|
|
return round_float(rippleZ);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* If movable, return the ripple function at (posX, posY)
|
|
|
|
* else return 0
|
|
|
|
*/
|
|
|
|
s16 ripple_if_movable(struct Painting *painting, s16 movable, s16 posX, s16 posY) {
|
|
|
|
s16 rippleZ = 0;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (movable) {
|
|
|
|
rippleZ = calculate_ripple_at_point(painting, posX, posY);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
return rippleZ;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Allocates and generates a mesh for the rippling painting effect by modifying the passed in `mesh`
|
|
|
|
* based on the painting's current ripple state.
|
|
|
|
*
|
|
|
|
* The `mesh` table describes the location of mesh vertices, whether they move when rippling, and what
|
|
|
|
* triangles they belong to.
|
|
|
|
*
|
|
|
|
* The static mesh passed in is organized into two lists. This function only uses the first list,
|
|
|
|
* painting_calculate_triangle_normals below uses the second one.
|
|
|
|
*
|
|
|
|
* The first list describes the vertices in this format:
|
|
|
|
* numVertices
|
|
|
|
* v0 x, v0 y, movable
|
|
|
|
* ...
|
|
|
|
* vN x, vN y, movable
|
|
|
|
* Where x and y are from 0 to PAINTING_SIZE, movable is 0 or 1.
|
|
|
|
*
|
|
|
|
* The mesh used in game, seg2_painting_triangle_mesh, is in bin/segment2.c.
|
|
|
|
*/
|
|
|
|
void painting_generate_mesh(struct Painting *painting, s16 *mesh, s16 numTris) {
|
|
|
|
s16 i;
|
|
|
|
|
|
|
|
gPaintingMesh = mem_pool_alloc(gEffectsMemoryPool, numTris * sizeof(struct PaintingMeshVertex));
|
|
|
|
if (gPaintingMesh == NULL) {
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
// accesses are off by 1 since the first entry is the number of vertices
|
|
|
|
for (i = 0; i < numTris; i++) {
|
|
|
|
gPaintingMesh[i].pos[0] = mesh[i * 3 + 1];
|
|
|
|
gPaintingMesh[i].pos[1] = mesh[i * 3 + 2];
|
|
|
|
// The "z coordinate" of each vertex in the mesh is either 1 or 0. Instead of being an
|
|
|
|
// actual coordinate, it just determines whether the vertex moves
|
|
|
|
gPaintingMesh[i].pos[2] = ripple_if_movable(painting, mesh[i * 3 + 3],
|
|
|
|
gPaintingMesh[i].pos[0], gPaintingMesh[i].pos[1]);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Calculate the surface normals of each triangle in the generated ripple mesh.
|
|
|
|
*
|
|
|
|
* The static mesh passed in is organized into two lists. This function uses the second list,
|
|
|
|
* painting_generate_mesh above uses the first one.
|
|
|
|
*
|
|
|
|
* The second list in `mesh` describes the mesh's triangles in this format:
|
|
|
|
* numTris
|
|
|
|
* tri0 v0, tri0 v1, tri0 v2
|
|
|
|
* ...
|
|
|
|
* triN v0, triN v1, triN v2
|
|
|
|
* Where each v0, v1, v2 is an index into the first list in `mesh`.
|
|
|
|
*
|
|
|
|
* The mesh used in game, seg2_painting_triangle_mesh, is in bin/segment2.c.
|
|
|
|
*/
|
|
|
|
void painting_calculate_triangle_normals(s16 *mesh, s16 numVtx, s16 numTris) {
|
|
|
|
s16 i;
|
|
|
|
|
|
|
|
gPaintingTriNorms = mem_pool_alloc(gEffectsMemoryPool, numTris * sizeof(Vec3f));
|
|
|
|
if (gPaintingTriNorms == NULL) {
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
for (i = 0; i < numTris; i++) {
|
|
|
|
s16 tri = numVtx * 3 + i * 3 + 2; // Add 2 because of the 2 length entries preceding the list
|
|
|
|
s16 v0 = mesh[tri];
|
|
|
|
s16 v1 = mesh[tri + 1];
|
|
|
|
s16 v2 = mesh[tri + 2];
|
|
|
|
|
|
|
|
f32 x0 = gPaintingMesh[v0].pos[0];
|
|
|
|
f32 y0 = gPaintingMesh[v0].pos[1];
|
|
|
|
f32 z0 = gPaintingMesh[v0].pos[2];
|
|
|
|
|
|
|
|
f32 x1 = gPaintingMesh[v1].pos[0];
|
|
|
|
f32 y1 = gPaintingMesh[v1].pos[1];
|
|
|
|
f32 z1 = gPaintingMesh[v1].pos[2];
|
|
|
|
|
|
|
|
f32 x2 = gPaintingMesh[v2].pos[0];
|
|
|
|
f32 y2 = gPaintingMesh[v2].pos[1];
|
|
|
|
f32 z2 = gPaintingMesh[v2].pos[2];
|
|
|
|
|
|
|
|
// Cross product to find each triangle's normal vector
|
|
|
|
gPaintingTriNorms[i][0] = (y1 - y0) * (z2 - z1) - (z1 - z0) * (y2 - y1);
|
|
|
|
gPaintingTriNorms[i][1] = (z1 - z0) * (x2 - x1) - (x1 - x0) * (z2 - z1);
|
|
|
|
gPaintingTriNorms[i][2] = (x1 - x0) * (y2 - y1) - (y1 - y0) * (x2 - x1);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Rounds a floating-point component of a normal vector to an s8 by multiplying it by 127 or 128 and
|
|
|
|
* rounding away from 0.
|
|
|
|
*/
|
|
|
|
s8 normalize_component(f32 comp) {
|
|
|
|
s8 rounded;
|
|
|
|
|
|
|
|
if (comp > 0.0) {
|
|
|
|
rounded = comp * 127.0 + 0.5; // round up
|
|
|
|
} else if (comp < 0.0) {
|
|
|
|
rounded = comp * 128.0 - 0.5; // round down
|
2019-09-01 19:50:50 +00:00
|
|
|
} else {
|
2020-03-02 03:42:52 +00:00
|
|
|
rounded = 0; // don't round 0
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
return rounded;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Approximates the painting mesh's vertex normals by averaging the normals of all triangles sharing a
|
|
|
|
* vertex. Used for gouraud lighting.
|
|
|
|
*
|
|
|
|
* After each triangle's surface normal is calculated, the `neighborTris` table describes which triangles
|
|
|
|
* each vertex should use when calculating the average normal vector.
|
|
|
|
*
|
|
|
|
* The table is a list of entries in this format:
|
|
|
|
* numNeighbors, tri0, tri1, ..., triN
|
|
|
|
*
|
|
|
|
* Where each 'tri' is an index into gPaintingTriNorms.
|
|
|
|
* Entry i in `neighborTris` corresponds to the vertex at gPaintingMesh[i]
|
|
|
|
*
|
|
|
|
* The table used in game, seg2_painting_mesh_neighbor_tris, is in bin/segment2.c.
|
|
|
|
*/
|
|
|
|
void painting_average_vertex_normals(s16 *neighborTris, s16 numVtx) {
|
2019-08-25 04:46:40 +00:00
|
|
|
UNUSED s16 unused;
|
2020-03-02 03:42:52 +00:00
|
|
|
s16 tri;
|
|
|
|
s16 i;
|
|
|
|
s16 j;
|
|
|
|
s16 neighbors;
|
|
|
|
s16 entry = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < numVtx; i++) {
|
|
|
|
f32 nx = 0.0f;
|
|
|
|
f32 ny = 0.0f;
|
|
|
|
f32 nz = 0.0f;
|
|
|
|
f32 nlen;
|
|
|
|
|
|
|
|
// The first number of each entry is the number of adjacent tris
|
|
|
|
neighbors = neighborTris[entry];
|
|
|
|
for (j = 0; j < neighbors; j++) {
|
|
|
|
tri = neighborTris[entry + j + 1];
|
|
|
|
nx += gPaintingTriNorms[tri][0];
|
|
|
|
ny += gPaintingTriNorms[tri][1];
|
|
|
|
nz += gPaintingTriNorms[tri][2];
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
// Move to the next vertex's entry
|
|
|
|
entry += neighbors + 1;
|
|
|
|
|
|
|
|
// average the surface normals from each neighboring tri
|
|
|
|
nx /= neighbors;
|
|
|
|
ny /= neighbors;
|
|
|
|
nz /= neighbors;
|
|
|
|
nlen = sqrtf(nx * nx + ny * ny + nz * nz);
|
|
|
|
|
|
|
|
if (nlen == 0.0) {
|
|
|
|
gPaintingMesh[i].norm[0] = 0;
|
|
|
|
gPaintingMesh[i].norm[1] = 0;
|
|
|
|
gPaintingMesh[i].norm[2] = 0;
|
2019-08-25 04:46:40 +00:00
|
|
|
} else {
|
2020-03-02 03:42:52 +00:00
|
|
|
gPaintingMesh[i].norm[0] = normalize_component(nx / nlen);
|
|
|
|
gPaintingMesh[i].norm[1] = normalize_component(ny / nlen);
|
|
|
|
gPaintingMesh[i].norm[2] = normalize_component(nz / nlen);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Creates a display list that draws the rippling painting, with 'img' mapped to the painting's mesh,
|
|
|
|
* using 'textureMap'.
|
|
|
|
*
|
|
|
|
* If the textureMap doesn't describe the whole mesh, then multiple calls are needed to draw the whole
|
|
|
|
* painting.
|
|
|
|
*/
|
|
|
|
Gfx *render_painting(u8 *img, s16 tWidth, s16 tHeight, s16 *textureMap, s16 mapVerts, s16 mapTris, u8 alpha) {
|
|
|
|
s16 group;
|
|
|
|
s16 map;
|
|
|
|
s16 triGroup;
|
|
|
|
s16 mapping;
|
|
|
|
s16 meshVtx;
|
2019-08-25 04:46:40 +00:00
|
|
|
s16 tx;
|
|
|
|
s16 ty;
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// We can fit 15 (16 / 3) vertices in the RSP's vertex buffer.
|
|
|
|
// Group triangles by 5, with one remainder group.
|
|
|
|
s16 triGroups = mapTris / 5;
|
|
|
|
s16 remGroupTris = mapTris % 5;
|
|
|
|
s16 numVtx = mapTris * 3;
|
|
|
|
|
|
|
|
s16 commands = triGroups * 2 + remGroupTris + 7;
|
|
|
|
Vtx *verts = alloc_display_list(numVtx * sizeof(Vtx));
|
|
|
|
Gfx *dlist = alloc_display_list(commands * sizeof(Gfx));
|
|
|
|
Gfx *gfx = dlist;
|
|
|
|
|
|
|
|
if (verts == NULL || dlist == NULL) {
|
|
|
|
}
|
|
|
|
|
|
|
|
gLoadBlockTexture(gfx++, tWidth, tHeight, G_IM_FMT_RGBA, img);
|
|
|
|
|
|
|
|
// Draw the groups of 5 first
|
|
|
|
for (group = 0; group < triGroups; group++) {
|
|
|
|
|
|
|
|
// The triangle groups are the second part of the texture map.
|
|
|
|
// Each group is a list of 15 mappings
|
|
|
|
triGroup = mapVerts * 3 + group * 15 + 2;
|
|
|
|
for (map = 0; map < 15; map++) {
|
|
|
|
// The mapping is just an index into the earlier part of the textureMap
|
|
|
|
// Some mappings are repeated, for example, when multiple triangles share a vertex
|
|
|
|
mapping = textureMap[triGroup + map];
|
|
|
|
|
|
|
|
// The first entry is the ID of the vertex in the mesh
|
|
|
|
meshVtx = textureMap[mapping * 3 + 1];
|
|
|
|
|
|
|
|
// The next two are the texture coordinates for that vertex
|
|
|
|
tx = textureMap[mapping * 3 + 2];
|
|
|
|
ty = textureMap[mapping * 3 + 3];
|
|
|
|
|
|
|
|
// Map the texture and place it in the verts array
|
|
|
|
make_vertex(verts, group * 15 + map, gPaintingMesh[meshVtx].pos[0], gPaintingMesh[meshVtx].pos[1],
|
|
|
|
gPaintingMesh[meshVtx].pos[2], tx, ty, gPaintingMesh[meshVtx].norm[0],
|
|
|
|
gPaintingMesh[meshVtx].norm[1], gPaintingMesh[meshVtx].norm[2], alpha);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// Load the vertices and draw the 5 triangles
|
|
|
|
gSPVertex(gfx++, VIRTUAL_TO_PHYSICAL(verts + group * 15), 15, 0);
|
|
|
|
gSPDisplayList(gfx++, dl_paintings_draw_ripples);
|
|
|
|
}
|
|
|
|
|
|
|
|
// One group left with < 5 triangles
|
|
|
|
triGroup = mapVerts * 3 + triGroups * 15 + 2;
|
|
|
|
// Map the texture to the triangles
|
|
|
|
for (map = 0; map < remGroupTris * 3; map++) {
|
|
|
|
mapping = textureMap[triGroup + map];
|
|
|
|
meshVtx = textureMap[mapping * 3 + 1];
|
|
|
|
tx = textureMap[mapping * 3 + 2];
|
|
|
|
ty = textureMap[mapping * 3 + 3];
|
|
|
|
make_vertex(verts, triGroups * 15 + map, gPaintingMesh[meshVtx].pos[0], gPaintingMesh[meshVtx].pos[1],
|
|
|
|
gPaintingMesh[meshVtx].pos[2], tx, ty, gPaintingMesh[meshVtx].norm[0],
|
|
|
|
gPaintingMesh[meshVtx].norm[1], gPaintingMesh[meshVtx].norm[2], alpha);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the triangles individually
|
|
|
|
gSPVertex(gfx++, VIRTUAL_TO_PHYSICAL(verts + triGroups * 15), remGroupTris * 3, 0);
|
|
|
|
for (group = 0; group < remGroupTris; group++) {
|
|
|
|
gSP1Triangle(gfx++, group * 3, group * 3 + 1, group * 3 + 2, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
gSPEndDisplayList(gfx);
|
|
|
|
return dlist;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Orient the painting mesh for rendering.
|
|
|
|
*/
|
|
|
|
Gfx *painting_model_view_transform(struct Painting *painting) {
|
|
|
|
f32 sizeRatio = painting->size / PAINTING_SIZE;
|
|
|
|
Mtx *rotX = alloc_display_list(sizeof(Mtx));
|
|
|
|
Mtx *rotY = alloc_display_list(sizeof(Mtx));
|
|
|
|
Mtx *translate = alloc_display_list(sizeof(Mtx));
|
|
|
|
Mtx *scale = alloc_display_list(sizeof(Mtx));
|
|
|
|
Gfx *dlist = alloc_display_list(5 * sizeof(Gfx));
|
|
|
|
Gfx *gfx = dlist;
|
|
|
|
|
|
|
|
if (rotX == NULL || rotY == NULL || translate == NULL || dlist == NULL) {
|
|
|
|
}
|
|
|
|
|
|
|
|
guTranslate(translate, painting->posX, painting->posY, painting->posZ);
|
|
|
|
guRotate(rotX, painting->pitch, 1.0f, 0.0f, 0.0f);
|
|
|
|
guRotate(rotY, painting->yaw, 0.0f, 1.0f, 0.0f);
|
|
|
|
guScale(scale, sizeRatio, sizeRatio, sizeRatio);
|
|
|
|
|
|
|
|
gSPMatrix(gfx++, translate, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_PUSH);
|
|
|
|
gSPMatrix(gfx++, rotX, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
|
|
|
|
gSPMatrix(gfx++, rotY, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
|
|
|
|
gSPMatrix(gfx++, scale, G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
|
|
|
|
gSPEndDisplayList(gfx);
|
|
|
|
|
|
|
|
return dlist;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ripple a painting that has 1 or more images that need to be mapped
|
|
|
|
*/
|
|
|
|
Gfx *painting_ripple_image(struct Painting *painting) {
|
|
|
|
s16 meshVerts;
|
|
|
|
s16 meshTris;
|
|
|
|
s16 i;
|
|
|
|
s16 *textureMap;
|
|
|
|
s16 imageCount = painting->imageCount;
|
2019-08-25 04:46:40 +00:00
|
|
|
s16 tWidth = painting->textureWidth;
|
|
|
|
s16 tHeight = painting->textureHeight;
|
2020-03-02 03:42:52 +00:00
|
|
|
s16 **textureMaps = segmented_to_virtual(painting->textureMaps);
|
|
|
|
u8 **textures = segmented_to_virtual(painting->textureArray);
|
|
|
|
Gfx *dlist = alloc_display_list((imageCount + 6) * sizeof(Gfx));
|
|
|
|
Gfx *gfx = dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (dlist == NULL) {
|
|
|
|
return dlist;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
gSPDisplayList(gfx++, painting_model_view_transform(painting));
|
|
|
|
gSPDisplayList(gfx++, dl_paintings_rippling_begin);
|
|
|
|
gSPDisplayList(gfx++, painting->rippleDisplayList);
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
// Map each image to the mesh's vertices
|
|
|
|
for (i = 0; i < imageCount; i++) {
|
|
|
|
textureMap = segmented_to_virtual(textureMaps[i]);
|
|
|
|
meshVerts = textureMap[0];
|
|
|
|
meshTris = textureMap[meshVerts * 3 + 1];
|
|
|
|
gSPDisplayList(gfx++, render_painting(textures[i], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha));
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// Update the ripple, may automatically reset the painting's state.
|
|
|
|
painting_update_ripple_state(painting);
|
|
|
|
|
|
|
|
gSPPopMatrix(gfx++, G_MTX_MODELVIEW);
|
|
|
|
gSPDisplayList(gfx++, dl_paintings_rippling_end);
|
|
|
|
gSPEndDisplayList(gfx);
|
|
|
|
return dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Ripple a painting that has 1 "environment map" texture.
|
|
|
|
*/
|
|
|
|
Gfx *painting_ripple_env_mapped(struct Painting *painting) {
|
|
|
|
s16 meshVerts;
|
|
|
|
s16 meshTris;
|
|
|
|
s16 *textureMap;
|
2019-08-25 04:46:40 +00:00
|
|
|
s16 tWidth = painting->textureWidth;
|
|
|
|
s16 tHeight = painting->textureHeight;
|
2020-03-02 03:42:52 +00:00
|
|
|
s16 **textureMaps = segmented_to_virtual(painting->textureMaps);
|
2019-08-25 04:46:40 +00:00
|
|
|
u8 **tArray = segmented_to_virtual(painting->textureArray);
|
2020-03-02 03:42:52 +00:00
|
|
|
Gfx *dlist = alloc_display_list(7 * sizeof(Gfx));
|
|
|
|
Gfx *gfx = dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (dlist == NULL) {
|
|
|
|
return dlist;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
gSPDisplayList(gfx++, painting_model_view_transform(painting));
|
|
|
|
gSPDisplayList(gfx++, dl_paintings_env_mapped_begin);
|
|
|
|
gSPDisplayList(gfx++, painting->rippleDisplayList);
|
|
|
|
|
|
|
|
// Map the image to the mesh's vertices
|
|
|
|
textureMap = segmented_to_virtual(textureMaps[0]);
|
|
|
|
meshVerts = textureMap[0];
|
|
|
|
meshTris = textureMap[meshVerts * 3 + 1];
|
|
|
|
gSPDisplayList(gfx++, render_painting(tArray[0], tWidth, tHeight, textureMap, meshVerts, meshTris, painting->alpha));
|
|
|
|
|
|
|
|
// Update the ripple, may automatically reset the painting's state.
|
|
|
|
painting_update_ripple_state(painting);
|
|
|
|
|
|
|
|
gSPPopMatrix(gfx++, G_MTX_MODELVIEW);
|
|
|
|
gSPDisplayList(gfx++, dl_paintings_env_mapped_end);
|
|
|
|
gSPEndDisplayList(gfx);
|
|
|
|
return dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Generates a mesh, calculates vertex normals for lighting, and renders a rippling painting.
|
|
|
|
* The mesh and vertex normals are regenerated and freed every frame.
|
|
|
|
*/
|
2019-11-03 19:36:27 +00:00
|
|
|
Gfx *display_painting_rippling(struct Painting *painting) {
|
2020-03-02 03:42:52 +00:00
|
|
|
s16 *mesh = segmented_to_virtual(seg2_painting_triangle_mesh);
|
|
|
|
s16 *neighborTris = segmented_to_virtual(seg2_painting_mesh_neighbor_tris);
|
|
|
|
s16 numVtx = mesh[0];
|
|
|
|
s16 numTris = mesh[numVtx * 3 + 1];
|
|
|
|
Gfx *dlist;
|
|
|
|
|
|
|
|
// Generate the mesh and its lighting data
|
|
|
|
painting_generate_mesh(painting, mesh, numVtx);
|
|
|
|
painting_calculate_triangle_normals(mesh, numVtx, numTris);
|
|
|
|
painting_average_vertex_normals(neighborTris, numVtx);
|
|
|
|
|
|
|
|
// Map the painting's texture depending on the painting's texture type.
|
|
|
|
switch (painting->textureType) {
|
|
|
|
case PAINTING_IMAGE:
|
|
|
|
dlist = painting_ripple_image(painting);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
2020-03-02 03:42:52 +00:00
|
|
|
case PAINTING_ENV_MAP:
|
|
|
|
dlist = painting_ripple_env_mapped(painting);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// The mesh data is freed every frame.
|
|
|
|
mem_pool_free(gEffectsMemoryPool, gPaintingMesh);
|
|
|
|
mem_pool_free(gEffectsMemoryPool, gPaintingTriNorms);
|
|
|
|
return dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Render a normal painting.
|
|
|
|
*/
|
2019-11-03 19:36:27 +00:00
|
|
|
Gfx *display_painting_not_rippling(struct Painting *painting) {
|
2020-03-02 03:42:52 +00:00
|
|
|
Gfx *dlist = alloc_display_list(4 * sizeof(Gfx));
|
|
|
|
Gfx *gfx = dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (dlist == NULL) {
|
|
|
|
return dlist;
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
gSPDisplayList(gfx++, painting_model_view_transform(painting));
|
|
|
|
gSPDisplayList(gfx++, painting->normalDisplayList);
|
|
|
|
gSPPopMatrix(gfx++, G_MTX_MODELVIEW);
|
|
|
|
gSPEndDisplayList(gfx);
|
|
|
|
return dlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Clear mario-related state and clear gRipplingPainting.
|
|
|
|
*/
|
2019-11-03 19:36:27 +00:00
|
|
|
void reset_painting(struct Painting *painting) {
|
2019-08-25 04:46:40 +00:00
|
|
|
painting->lastFloor = 0;
|
|
|
|
painting->currFloor = 0;
|
|
|
|
painting->floorEntered = 0;
|
2020-03-02 03:42:52 +00:00
|
|
|
painting->marioWasUnder = 0;
|
|
|
|
painting->marioIsUnder = 0;
|
|
|
|
painting->marioWentUnder = 0;
|
|
|
|
|
|
|
|
gRipplingPainting = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Controls the x coordinate of the DDD painting.
|
|
|
|
*
|
|
|
|
* Before mario gets the "Board Bowser's Sub" star in DDD, the painting spawns at frontPos.
|
|
|
|
*
|
|
|
|
* If mario just got the star, the painting's x coordinate moves to backPos at a rate of `speed` units.
|
|
|
|
*
|
|
|
|
* When the painting reaches backPos, a save flag is set so that the painting will spawn at backPos
|
|
|
|
* whenever it loads.
|
|
|
|
*
|
|
|
|
* This function also sets gDddPaintingStatus, which controls the warp:
|
|
|
|
* 0 (0b00): set x coordinate to frontPos
|
|
|
|
* 2 (0b10): set x coordinate to backPos
|
|
|
|
* 3 (0b11): same as 2. Bit 0 is ignored
|
|
|
|
*/
|
|
|
|
void move_ddd_painting(struct Painting *painting, f32 frontPos, f32 backPos, f32 speed) {
|
|
|
|
// Obtain the DDD star flags
|
|
|
|
u32 dddFlags = save_file_get_star_flags(gCurrSaveFileNum - 1, COURSE_DDD - 1);
|
|
|
|
// Get the other save file flags
|
|
|
|
u32 saveFileFlags = save_file_get_flags();
|
|
|
|
// Find out whether Board Bowser's Sub was collected
|
|
|
|
u32 bowsersSubBeaten = dddFlags & BOARD_BOWSERS_SUB;
|
|
|
|
// Check whether DDD has already moved back
|
|
|
|
u32 dddBack = saveFileFlags & SAVE_FLAG_DDD_MOVED_BACK;
|
2019-08-25 04:46:40 +00:00
|
|
|
|
|
|
|
if (!bowsersSubBeaten && !dddBack) {
|
2020-03-02 03:42:52 +00:00
|
|
|
// If we haven't collected the star or moved the painting, put the painting at the front
|
|
|
|
painting->posX = frontPos;
|
|
|
|
gDddPaintingStatus = 0;
|
|
|
|
} else if (bowsersSubBeaten && !dddBack) {
|
|
|
|
// If we've collected the star but not moved the painting back,
|
|
|
|
// Each frame, move the painting by a certain speed towards the back area.
|
|
|
|
painting->posX += speed;
|
|
|
|
gDddPaintingStatus = BOWSERS_SUB_BEATEN;
|
|
|
|
if (painting->posX >= backPos) {
|
|
|
|
painting->posX = backPos;
|
|
|
|
// Tell the save file that we've moved DDD back.
|
|
|
|
save_file_set_flags(SAVE_FLAG_DDD_MOVED_BACK);
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
} else if (bowsersSubBeaten && dddBack) {
|
2020-03-02 03:42:52 +00:00
|
|
|
// If the painting has already moved back, place it in the back position.
|
|
|
|
painting->posX = backPos;
|
|
|
|
gDddPaintingStatus = BOWSERS_SUB_BEATEN | DDD_BACK;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Set the painting's node's layer based on its alpha
|
|
|
|
*/
|
|
|
|
void set_painting_layer(struct GraphNodeGenerated *gen, struct Painting *painting) {
|
|
|
|
switch (painting->alpha) {
|
|
|
|
case 0xFF: // Opaque
|
|
|
|
gen->fnNode.node.flags = (gen->fnNode.node.flags & 0xFF) | (LAYER_OPAQUE << 8);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
default:
|
2020-03-02 03:42:52 +00:00
|
|
|
gen->fnNode.node.flags = (gen->fnNode.node.flags & 0xFF) | (LAYER_TRANSPARENT << 8);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Display either a normal painting or a rippling one depending on the painting's ripple status
|
|
|
|
*/
|
2019-11-03 19:36:27 +00:00
|
|
|
Gfx *display_painting(struct Painting *painting) {
|
2020-03-02 03:42:52 +00:00
|
|
|
switch (painting->state) {
|
|
|
|
case PAINTING_IDLE:
|
2019-08-25 04:46:40 +00:00
|
|
|
return display_painting_not_rippling(painting);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return display_painting_rippling(painting);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Update function for wall paintings.
|
|
|
|
* Calls a different update function depending on the painting's ripple trigger and current state.
|
|
|
|
*/
|
|
|
|
void wall_painting_update(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
if (painting->rippleTrigger == RIPPLE_TRIGGER_PROXIMITY) {
|
|
|
|
switch (painting->state) {
|
|
|
|
case PAINTING_IDLE:
|
|
|
|
wall_painting_proximity_idle(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
2020-03-02 03:42:52 +00:00
|
|
|
case PAINTING_RIPPLE:
|
|
|
|
wall_painting_proximity_rippling(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (painting->rippleTrigger == RIPPLE_TRIGGER_CONTINUOUS) {
|
2020-03-02 03:42:52 +00:00
|
|
|
switch (painting->state) {
|
|
|
|
case PAINTING_IDLE:
|
|
|
|
wall_painting_continuous_idle(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
2020-03-02 03:42:52 +00:00
|
|
|
case PAINTING_RIPPLE:
|
|
|
|
wall_painting_continuous_rippling(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Update function for floor paintings (HMC and CotMC)
|
|
|
|
* Calls a different update function depending on the painting's ripple trigger and current state.
|
|
|
|
*
|
|
|
|
* No floor paintings use RIPPLE_TRIGGER_PROXIMITY in the game.
|
|
|
|
*/
|
|
|
|
void floor_painting_update(struct Painting *painting, struct Painting *paintingGroup[]) {
|
|
|
|
if (painting->rippleTrigger == RIPPLE_TRIGGER_PROXIMITY) {
|
|
|
|
switch (painting->state) {
|
|
|
|
case PAINTING_IDLE:
|
|
|
|
floor_painting_proximity_idle(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
2020-03-02 03:42:52 +00:00
|
|
|
case PAINTING_RIPPLE:
|
|
|
|
floor_painting_proximity_rippling(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (painting->rippleTrigger == RIPPLE_TRIGGER_CONTINUOUS) {
|
2020-03-02 03:42:52 +00:00
|
|
|
switch (painting->state) {
|
|
|
|
case PAINTING_IDLE:
|
|
|
|
floor_painting_continuous_idle(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
2020-03-02 03:42:52 +00:00
|
|
|
case PAINTING_RIPPLE:
|
|
|
|
floor_painting_continuous_rippling(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Render and update the painting whose id and group matches the values in the GraphNode's parameter.
|
|
|
|
* Use PAINTING_ID(id, group) to set the right parameter in a level's geo layout.
|
|
|
|
*/
|
|
|
|
Gfx *geo_painting_draw(s32 callContext, struct GraphNode *node, UNUSED void *context) {
|
|
|
|
struct GraphNodeGenerated *gen = (struct GraphNodeGenerated *) node;
|
|
|
|
s32 group = (gen->parameter >> 8) & 0xFF;
|
|
|
|
s32 id = gen->parameter & 0xFF;
|
|
|
|
Gfx *paintingDlist = NULL;
|
|
|
|
struct Painting **paintingGroup = sPaintingGroups[group];
|
2019-11-03 19:36:27 +00:00
|
|
|
struct Painting *painting = segmented_to_virtual(paintingGroup[id]);
|
2019-08-25 04:46:40 +00:00
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
if (callContext != GEO_CONTEXT_RENDER) {
|
2019-08-25 04:46:40 +00:00
|
|
|
reset_painting(painting);
|
2020-03-02 03:42:52 +00:00
|
|
|
} else if (callContext == GEO_CONTEXT_RENDER) {
|
|
|
|
|
|
|
|
// Update the ddd painting before drawing
|
|
|
|
if (group == 1 && id == PAINTING_ID_DDD) {
|
|
|
|
move_ddd_painting(painting, 3456.0f, 5529.6f, 20.0f);
|
2019-09-01 19:50:50 +00:00
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// Determine if the painting is transparent
|
|
|
|
set_painting_layer(gen, painting);
|
|
|
|
|
|
|
|
// Draw before updating
|
|
|
|
paintingDlist = display_painting(painting);
|
|
|
|
|
|
|
|
// Update the painting
|
2019-08-25 04:46:40 +00:00
|
|
|
painting_update_floors(painting);
|
2020-03-02 03:42:52 +00:00
|
|
|
switch ((s16) painting->pitch) {
|
|
|
|
// only paintings with 0 pitch are treated as walls
|
|
|
|
case 0:
|
|
|
|
wall_painting_update(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
default:
|
2020-03-02 03:42:52 +00:00
|
|
|
floor_painting_update(painting, paintingGroup);
|
2019-08-25 04:46:40 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-03-02 03:42:52 +00:00
|
|
|
return paintingDlist;
|
2019-08-25 04:46:40 +00:00
|
|
|
}
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
/**
|
|
|
|
* Update the painting system's local copy of mario's current floor and position.
|
|
|
|
*/
|
|
|
|
Gfx *geo_painting_update(s32 callContext, UNUSED struct GraphNode *node, UNUSED f32 c[4][4]) {
|
2019-08-25 04:46:40 +00:00
|
|
|
struct Surface *surface;
|
|
|
|
|
2020-03-02 03:42:52 +00:00
|
|
|
// Reset the update counter
|
|
|
|
if (callContext != GEO_CONTEXT_RENDER) {
|
2019-08-25 04:46:40 +00:00
|
|
|
gLastPaintingUpdateCounter = gAreaUpdateCounter - 1;
|
|
|
|
gPaintingUpdateCounter = gAreaUpdateCounter;
|
|
|
|
} else {
|
|
|
|
gLastPaintingUpdateCounter = gPaintingUpdateCounter;
|
|
|
|
gPaintingUpdateCounter = gAreaUpdateCounter;
|
2020-03-02 03:42:52 +00:00
|
|
|
|
|
|
|
// Store mario's floor and position
|
2019-08-25 04:46:40 +00:00
|
|
|
find_floor(gMarioObject->oPosX, gMarioObject->oPosY, gMarioObject->oPosZ, &surface);
|
|
|
|
gPaintingMarioFloorType = surface->type;
|
|
|
|
gPaintingMarioXPos = gMarioObject->oPosX;
|
|
|
|
gPaintingMarioYPos = gMarioObject->oPosY;
|
|
|
|
gPaintingMarioZPos = gMarioObject->oPosZ;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|