Section: Character Intro 2
Quick Edit
POOL TABLE DEMO
Design R&D
Objective:
To explore how Rive can be extended beyond its native capabilities by syncing it with external physics logic (Matter.js). Also to better understand how Rive integrates into broader interactive systems, in this case, for real-time, web-based UIs (using state machines, and data binding inputs).
Approach
- Built a small pool table prototype using Rive, Matter.js, and p5.js within a React web app.
- Used Rive’s state machines and new data binding feature to drive visual feedback based on real-time physics simulation.
- Used p5.js for visual input and guides; Matter.js handled the physics (collision, movement and gravity).
What Worked
- Data binding in Rive allowed for smooth real-time animation syncing.
Visuals and UI
- Used Rive's new WebGL vector feathering, which added smoother visuals - wanted to go for a clean, dynamic, and modern style.
Challenges & Learnings
- Rive doesn't currently support physics natively, so syncing had to be hardcoded through triggers and inputs.
- Mobile support was inconsistent, especially around input events and canvas rendering.
- Some interactions (like hover states) couldn’t be triggered natively from Rive due to layering constraints, was able to call on inputs via the State Machine but had to simulate them via code.
- These technical hurdles deepened my understanding of how Rive’s state machines can be integrated into React-based systems and paired with external logic layers.
Takeaway
This was an experimental build - more systems research than high-fidelity product prototyping. It pushed my understanding of how Rive could be part of interactive ecosystems beyond its standard use case.
link to demo (web only for now): https://rivepooldemo.vercel.app/
Design R&D
Objective:
To explore how Rive can be extended beyond its native capabilities by syncing it with external physics logic (Matter.js). Also to better understand how Rive integrates into broader interactive systems, in this case, for real-time, web-based UIs (using state machines, and data binding inputs).
Approach
- Built a small pool table prototype using Rive, Matter.js, and p5.js within a React web app.
- Used Rive’s state machines and new data binding feature to drive visual feedback based on real-time physics simulation.
- Used p5.js for visual input and guides; Matter.js handled the physics (collision, movement and gravity).
What Worked
- Data binding in Rive allowed for smooth real-time animation syncing.
Visuals and UI
- Used Rive's new WebGL vector feathering, which added smoother visuals - wanted to go for a clean, dynamic, and modern style.
Challenges & Learnings
- Rive doesn't currently support physics natively, so syncing had to be hardcoded through triggers and inputs.
- Mobile support was inconsistent, especially around input events and canvas rendering.
- Some interactions (like hover states) couldn’t be triggered natively from Rive due to layering constraints, was able to call on inputs via the State Machine but had to simulate them via code.
- These technical hurdles deepened my understanding of how Rive’s state machines can be integrated into React-based systems and paired with external logic layers.
Takeaway
This was an experimental build - more systems research than high-fidelity product prototyping. It pushed my understanding of how Rive could be used as a part of an interactive ecosystems beyond its standard motion use case.
link to demo (web only for now): https://rivepooldemo.vercel.app/
Design R&D
Objective:
To explore how Rive can be extended beyond its native capabilities by syncing it with external physics logic (Matter.js). Also to better understand how Rive integrates into broader interactive systems, in this case, for real-time, web-based UIs (using state machines, and data binding inputs).
Approach
- Built a small pool table prototype using Rive, Matter.js, and p5.js within a React web app.
- Used Rive’s state machines and new data binding feature to drive visual feedback based on real-time physics simulation.
- Used p5.js for visual input and guides; Matter.js handled the physics (collision, movement and gravity).
What Worked
- Data binding in Rive allowed for smooth real-time animation syncing.
Visuals and UI
- Used Rive's new WebGL vector feathering, which added smoother visuals - wanted to go for a clean, dynamic, and modern style.
Challenges & Learnings
- Rive doesn't currently support physics natively, so syncing had to be hardcoded through triggers and inputs.
- Mobile support was inconsistent, especially around input events and canvas rendering.
- Some interactions (like hover states) couldn’t be triggered natively from Rive due to layering constraints, was able to call on inputs via the State Machine but had to simulate them via code.
- These technical hurdles deepened my understanding of how Rive’s state machines can be integrated into React-based systems and paired with external logic layers.
Takeaway
This was an experimental build - more systems research than high-fidelity product prototyping. It pushed my understanding of how Rive could be used as a part of an interactive ecosystems beyond its standard motion use case.
link to demo (web only for now): https://rivepooldemo.vercel.app/
Video Demo:
Using Rive's React-webGL2 Renderer



@rive-app/react-webgl2 was the only production-ready package that easily supports real-time vector feathering in React
<- rendered using @rive-app/react-canvas
^ rendered using @rive-app/react-canvas
@rive-app/react-webgl2 was the only production-ready package that easily supports real-time vector feathering in React
Initial State Machine Set Up
My initial plan was to use a state machine to gather the X and Y coordinates of each ball in Rive, normalize those values from -100 to 100, and then feed them into Matter.js for physics simulation.
Challenges:
Rive currently doesn’t support binding data from timeline animations directly into inputs accessible via JavaScript. This meant I couldn't directly extract animation-driven values (like position) and pass them to Matter.js.
My solution was to use additive blends, one that tracked the coordinates of X and one for Y with each blend containing two input types (one for a value, and one for an input)
This allowed me to create a bidirectional motion system. Rive currently only supports unidirectional value inputs by default (0-100). This meant, to handle the "negative values" I would need to use an additional "input" input type that tracked the range between (-100 - 0). This allowed me to simulate full -100 to +100 range control in both axes - this worked!!! (sort of)
The Problem:
Using the "any state" node allowed this set up to plug into JS and send data to and from however this also caused an infinite loop with the state machine "exceeding max iterations" - without proper conditions or gates this kept failing and was highly unstable.
Troubleshooting:
Adding a buffer or an "idle state" between the "any state" node and the blend states - the problem: without the "any state" node being directly connected to the blend states, the animation would only choose one path, meaning only either X or the Y would get moved.
Adding a boolean input to act as a gate before entering the loop - this worked to move the position of the ball, however results were unstable and could not be scaled manageably without other complicated conditions.
Takeaway:
This process taught me a lot about the nuances of Rive’s state machine architecture — especially its limitations around dynamic input binding, bidirectional control, and real-time feedback loops. While I wasn’t able to create a fully stable dynamic multi-object system, this experimentation revealed key integration friction points and helped map out what’s possible (and not yet possible) when syncing animation logic with external engines like Matter.js.
My initial plan was to use a state machine to gather the X and Y coordinates of each ball in Rive, normalize those values from -100 to 100, and then feed them into Matter.js for physics simulation.
Challenges:
Rive currently doesn’t support binding data from timeline animations directly into inputs accessible via JavaScript. This meant I couldn't directly extract animation-driven values (like position) and pass them to Matter.js.
My solution was to use additive blends, one that tracked the coordinates of X and one for Y with each blend containing two input types (one for a value, and one for an input)
This allowed me to create a bidirectional motion system. Rive currently only supports unidirectional value inputs by default (0-100). This meant, to handle the "negative values" I would need to use an additional "input" input type that tracked the range between (-100 - 0). This allowed me to simulate full -100 to +100 range control in both axes - this worked!!! (sort of)
The Problem:
Using the "any state" node allowed this set up to plug into JS and send data to and from however this also caused an infinite loop with the state machine "exceeding max iterations" - without proper conditions or gates this kept failing and was highly unstable.
Troubleshooting:
Adding a buffer or an "idle state" between the "any state" node and the blend states - the problem: without the "any state" node being directly connected to the blend states, the animation would only choose one path, meaning only either X or the Y would get moved.
Adding a boolean input to act as a gate before entering the loop - this worked to move the position of the ball, however results were unstable and could not be scaled manageably without other complicated conditions.
Takeaway:
This process taught me a lot about the nuances of Rive’s state machine architecture — especially its limitations around dynamic input binding, bidirectional control, and real-time feedback loops. While I wasn’t able to create a fully stable dynamic multi-object system, this experimentation revealed key integration friction points and helped map out what’s possible (and not yet possible) when syncing animation logic with external engines like Matter.js.



SOLUTION - what worked
SOLUTION - what worked
Using Data Binding to send X,Y Ball coordinates to JS
Using Data Binding to send X,Y Ball coordinates to JS



( S/O Rive's Discord community )
( S/O Rive's Discord community )

After days of trial and error with the state machine setup - I turned to the Rive Discord community for help. Initially, I had dismissed Rive's new data binding feature due to what I had thought was it's limitation to only read data but not set any, which would make it unusable for real time syncing.
But then, someone pointed me to one tiny, easy-to-miss, note in their documentation about "Mutable Properties" which would allow me to "get" and "set" data. This was *chef's kiss* - perfect. Exactly what I needed.
Scaling the System:
After some initial set up and discovery with this approach, I was able to scale this system across all objects (16 balls) and move them in real time across both Rive and matter.js.
Big thank you to Rive's discord community.
After days of trial and error with the state machine setup - I turned to the Rive Discord community for help. Initially, I had dismissed Rive's new data binding feature due to what I had thought was it's limitation to only read data but not set any, which would make it unusable for real time syncing.
But then, someone pointed me to one small, easy-to-miss, note in their documentation about "Mutable Properties" which would allow me to "get" and "set" data. This was *chef's kiss* - perfect. Exactly what I needed.
Scaling the System:
After some initial set up and discovery with this approach, I was able to scale this system across all objects (16 balls) and move them in real time across both Rive and matter.js.
Big thank you to Rive's discord community.
Syncing visuals in P5 with Rive
In this setup, matter.js handled all of the physics logic (collision, gravity, and motion) - while p5.js acted as a visual input layer between Rive objects and the physics engine.
This meant I needed to perfectly align the p5 drawn visuals with the Rive objects so that the interactions felt seamless, and the movement of each object remained consistent across all layers.
Some challenges:
Rive uses a center-based coordinate system which put (0,0) in the center of the canvas,
p5.js uses a top-left coordinate system which puts (0,0) in the upper-left corner.
This mismatch required manual calibration and offsetting to align object positions correctly between the two systems. Even small discrepancies created noticeable desyncs, so I had to be precise in normalizing coordinates across both environments.
In this setup, matter.js handled all of the physics logic (collision, gravity, and motion) - while p5.js acted as a visual input layer between Rive objects and the physics engine.
This meant I needed to perfectly align the p5 drawn visuals with the Rive objects so that the interactions felt seamless, and the movement of each object remained consistent across all layers.
Some challenges:
Rive uses a center-based coordinate system which put (0,0) in the center of the canvas,
p5.js uses a top-left coordinate system which puts (0,0) in the upper-left corner.
This mismatch required manual calibration and offsetting to align object positions correctly between the two systems. Even small discrepancies created noticeable desyncs, so I had to be precise in normalizing coordinates across both environments.









Adding Tray
Adding Tray
The original gameplay vision was for sunken balls to disappear entirely from the game and for the cue ball to reset to its starting position if pocketed.
However, ran into limitations with Matter.js when trying to fully remove objects from the physics world and re-render the scene smoothly in p5.js. Deleting objects mid-simulation was causing inconsistencies and glitches, especially as the game progressed.
A solution was to introduce a "tray" beneath the table- a visual container where sunken balls would be moved instead of deleted. This approach kept the physics stable while also introducing a beautiful UI touch - a delightful design moment: the tray became a dynamic visual log of your gameplay, with colored balls stacking in a randomized order that reflected your unique sequence of shots.
The original gameplay vision was for sunken balls to disappear entirely from the game and for the cue ball to reset to its starting position if pocketed.
However, ran into limitations with Matter.js when trying to fully remove objects from the physics world and re-render the scene smoothly in p5.js. Deleting objects mid-simulation was causing inconsistencies and glitches, especially as the game progressed.
A solution was to introduce a "tray" beneath the table- a visual container where sunken balls would be moved instead of deleted. This approach kept the physics stable while also introducing a beautiful UI touch - a delightful design moment: the tray became a dynamic visual log of your gameplay, with colored balls stacking in a randomized order that reflected your unique sequence of shots.



INTERACTIONS HANDLED IN RIVE (STATE MACHINE) VS. P5.JS
INTERACTIONS HANDLED IN
RIVE (STATE MACHINE) VS. P5.JS
One limitation of this setup, was that I couldn't fully take advantage of Rive's built in interactivity features, including native hover states. This was due to some layering constraints, with the p5.js layer sitting above the Rive canvas - which intercepted pointer events that would handle hover states such as "mouse enter" and "mouse exit" calls in Rive.
The workaround was to simulate these hover animations by triggering state machine inputs in code.
This allowed interactivity using Rive as it was intended for with the 8 ball endgame modal with different inputs handling the visibility and hover events.
For the "rerack" button - I opted to handle this entirely within p5.js. Since the button was visually minimal and tightly coupled with game logic, this approach avoided unnecessary complexity between systems while keeping the UX clean and responsive.
One limitation of this setup, was that I couldn't fully take advantage of Rive's built in interactivity features, including native hover states. This was due to some layering constraints, with the p5.js layer sitting above the Rive canvas - which intercepted pointer events that would handle hover states such as "mouse enter" and "mouse exit" calls in Rive.
The workaround was to simulate these hover animations by triggering state machine inputs in code.
This allowed interactivity using Rive as it was intended for with the 8 ball endgame modal with different inputs handling the visibility and hover events.
For the "rerack" button - I opted to handle this entirely within p5.js. Since the button was visually minimal and tightly coupled with game logic, this approach avoided unnecessary complexity between systems while keeping the UX clean and responsive.
Adding 8 Ball modal (state machine)
Adding 8 Ball modal (state machine)
Adding 8 Ball modal (state machine)






Adding a re-rack button in p5.js
Adding a re-rack button in p5.js



Testing UI elements - (helper text placement, titles, etc.)
Testing UI elements -
(helper text placement, titles, etc.)






Final UI - Test + Deploy! :)
Final UI - Test + Deploy! :)



-> Play the demo: https://rivepooldemo.vercel.app/
-> Play the demo: