Skip to content

Commit

Permalink
feat(2d): support colors and gradients
Browse files Browse the repository at this point in the history
  • Loading branch information
Gugustinette committed Nov 21, 2024
1 parent 785577b commit b496854
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 11 deletions.
92 changes: 92 additions & 0 deletions apps/playground-2d/components/Navbar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export class Navbar {
private __DOM__: HTMLElement
private __DOM_TOGGLE__: HTMLElement

constructor() {
// Create the navbar
this.__DOM__ = document.createElement('nav')
this.__DOM__.id = 'demo-navbar'
this.__DOM__.innerHTML = `
<a href="/playground-2d/">Home</a>
<a href="/playground-2d/demos/squairbows/">Squairbows</a>
<style>
#demo-navbar {
position: fixed;
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
width: 180px;
height: 90vh;
background-color: rgba(0, 0, 0, 0.6);
color: white;
border-radius: 0 10px 10px 0;
transform: translateX(-100%);
transition: transform 0.3s ease;
}
#demo-navbar a {
width: 100%;
color: white;
padding: 0.5rem 1rem;
text-decoration: none;
background-color: transparent;
transition: background-color 0.3s ease;
}
#demo-navbar a:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* Gets applied if supported only */
@supports (
(-webkit-backdrop-filter: blur(8px)) or (backdrop-filter: blur(8px))
) {
#demo-navbar {
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
}
</style>
`

// Create the toggle button
this.__DOM_TOGGLE__ = document.createElement('div')
this.__DOM_TOGGLE__.id = 'demo-navbar-toggle'
this.__DOM_TOGGLE__.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="-5 -7 24 24"><path fill="currentColor" d="M1 0h5a1 1 0 1 1 0 2H1a1 1 0 1 1 0-2m7 8h5a1 1 0 0 1 0 2H8a1 1 0 1 1 0-2M1 4h12a1 1 0 0 1 0 2H1a1 1 0 1 1 0-2"/></svg>
<style>
#demo-navbar-toggle {
position: absolute;
top: 1rem;
left: 1rem;
width: 2rem;
height: 2rem;
background-color: rgba(0, 0, 0, 0.6);
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
#demo-navbar-toggle svg {
width: 1.5rem;
height: 1.5rem;
fill: white;
}
#demo-navbar-toggle:focus {
outline: none;
}
</style>
`
let toggle = false
this.__DOM_TOGGLE__.addEventListener('click', () => {
this.__DOM__.style.transform = toggle ? 'translateX(-100%)' : 'translateX(0)'
toggle = !toggle
})

document.body.appendChild(this.__DOM__)
document.body.appendChild(this.__DOM_TOGGLE__)
}
}
12 changes: 12 additions & 0 deletions apps/playground-2d/demos/squairbows/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body>
<script type="module" src="/demos/squairbows/main.ts"></script>
</body>
</html>
103 changes: 103 additions & 0 deletions apps/playground-2d/demos/squairbows/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import '../../src/style.css'
import { FFixedCamera, FRectangle, FScene } from '@fibbojs/2d'
import { Navbar } from '../../components/Navbar'

function hslToRgb(h: number, s: number, l: number): [number, number, number] {
// Normalize values
h /= 360
s /= 100
l /= 100

let r, g, b

if (s === 0) {
r = g = b = l // achromatic
}
else {
const hue2rgb = (p: number, q: number, t: number) => {
if (t < 0)
t += 1
if (t > 1)
t -= 1
if (t < 1 / 6)
return p + (q - p) * 6 * t
if (t < 1 / 2)
return q
if (t < 2 / 3)
return p + (q - p) * (2 / 3 - t) * 6
return p
}

const q = l < 0.5 ? l * (1 + s) : l + s - l * s
const p = 2 * l - q
r = hue2rgb(p, q, h + 1 / 3)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h - 1 / 3)
}

return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]
}

(async () => {
new Navbar()

const scene = new FScene()
await scene.init()
await scene.initPhysics()

// Create a matrix to hold the squares
const matrix = [] as FRectangle[][]

// Create a grid of 40x40 squares
const GRID_ROWS = 40
const GRID_COLS = 40
const GRID_GAP = 0.5
for (let i = 0; i < GRID_ROWS; i++) {
for (let j = 0; j < GRID_COLS; j++) {
const square = new FRectangle(scene, {
position: { x: i * GRID_GAP - GRID_ROWS * GRID_GAP / 2, y: j * GRID_GAP - GRID_COLS * GRID_GAP / 2 },
scale: { x: 0.5, y: 0.5 },
color: 0x2C2C2C,
})
square.initSensor()
square.__CONTAINER__.eventMode = 'static'
square.__CONTAINER__.on('mouseenter', () => {
// Color closest squares to the mouse depending on the distance
square.color = 0x00FF00
const distance = 8
for (let x = Math.max(0, i - distance); x <= Math.min(GRID_ROWS - 1, i + distance); x++) {
for (let y = Math.max(0, j - distance); y <= Math.min(GRID_COLS - 1, j + distance); y++) {
const dist = Math.sqrt((x - i) ** 2 + (y - j) ** 2)
if (dist <= distance) {
// Normalize dist to be between 0 and 1
const normalizedDist = dist / distance
// Convert normalized distance to hue (0-360)
const hue = normalizedDist * 360
// Convert HSL to RGB
const rgb = hslToRgb(hue, 90, 70)
matrix[x][y].color = (rgb[0] << 16) + (rgb[1] << 8) + rgb[2]
}
}
}
})
square.__CONTAINER__.on('mouseleave', () => {
// Reset the color and rotation of all squares
square.color = 0x2C2C2C
for (let x = 0; x < GRID_ROWS; x++) {
for (let y = 0; y < GRID_COLS; y++) {
matrix[x][y].color = 0x2C2C2C
}
}
})
if (matrix[i] === undefined)
matrix[i] = []
matrix[i][j] = square
}
}

// Create a fixed camera
scene.camera = new FFixedCamera(scene, {
position: { x: 0, y: -0.2 },
})
scene.camera.setZoom(0.5)
})()
7 changes: 7 additions & 0 deletions apps/playground-2d/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import './style.css'
import { FAttachedCamera, FCircle, FComponentEmpty, FRectangle, FScene, FShapes } from '@fibbojs/2d'
import { fDebug } from '@fibbojs/devtools'
import { FKeyboard } from '@fibbojs/event'
import { Navbar } from '../components/Navbar'
import MySquare from './classes/MySquare'
import { loadLevel } from './level'
import Character from './classes/Character'

(async () => {
new Navbar()

const scene = new FScene()
await scene.init()
await scene.initPhysics()
Expand Down Expand Up @@ -61,6 +64,10 @@ import Character from './classes/Character'
const circle = new FCircle(scene, {
position: { x: 0, y: 3 },
scale: { x: 1, y: 1 },
gradient: [
{ position: 0, color: 0x0000FF },
{ position: 1, color: 0xFFFF00 },
],
})
circle.initRigidBody()

Expand Down
5 changes: 5 additions & 0 deletions apps/playground-2d/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
import wasm from 'vite-plugin-wasm'
import topLevelAwait from 'vite-plugin-top-level-await'
Expand All @@ -10,6 +11,10 @@ export default defineConfig({
base: '/playground-2d/',
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
squairbows: resolve(__dirname, 'demos/squairbows/index.html'),
},
// Solution found here: https://github.com/dimforge/rapier.js/issues/278
// Without this option, treeshaking seems to ditch required code from Rapier
// Basically results in : "TypeError: Cannot read properties of undefined (reading 'rawintegrationparameters_new')"
Expand Down
3 changes: 2 additions & 1 deletion apps/playground-3d/demos/rainbowls/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import * as THREE from 'three'
import { Navbar } from '../../components/Navbar'

(async () => {
new Navbar()

const scene = new FScene()
scene.init()
new Navbar()

// Add ambient light
new FAmbientLight(scene, {
Expand Down
3 changes: 2 additions & 1 deletion apps/playground-3d/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import MyCustomCube from './classes/MyCustomCube'
import Character from './classes/Character'

(async () => {
new Navbar()

// Initialize the scene
const scene = new FScene({
shadows: true,
Expand All @@ -17,7 +19,6 @@ import Character from './classes/Character'
await scene.initPhysics()
if (import.meta.env.DEV)
fDebug(scene)
new Navbar()

// Add directional light to represent the sun
new FDirectionalLight(scene, {
Expand Down
23 changes: 19 additions & 4 deletions packages/2d/src/polygons/FCircle.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as PIXI from 'pixi.js'
import type { FComponentOptions } from '../core/FComponent'
import type { FScene } from '../core/FScene'
import { FShapes } from '../types/FShapes'
import type { FRigidBodyOptions } from '../core/FRigidBody'
import type { FColliderOptions } from '../core/FCollider'
import type { FPolygonOptions } from './FPolygon'
import { FPolygon } from './FPolygon'

/**
Expand All @@ -20,12 +20,23 @@ import { FPolygon } from './FPolygon'
* ```
*/
export class FCircle extends FPolygon {
constructor(scene: FScene, options?: FComponentOptions) {
constructor(scene: FScene, options?: FPolygonOptions) {
super(scene, options)
// Create a circle
// Create the circle
this.__CONTAINER__ = new PIXI.Graphics()
.circle(this.transform.position.x, this.transform.position.y, this.transform.scale.x * 100 / 2)
.fill(new PIXI.FillGradient(0, 0, 10, 10).addColorStop(0, 0x0000FF).addColorStop(1, 0xFFFF00))

// If a gradient was provided, use it to fill the circle
if (this.__GRADIENT__ !== undefined) {
const gradient = new PIXI.FillGradient(0, 0, this.transform.scale.x * 10, this.transform.scale.y * 10)
this.__GRADIENT__.forEach(step => gradient.addColorStop(step.position, step.color))
this.__CONTAINER__.fill(gradient)
}
// Else if a color was provided, use it to fill the circle
else if (this.__COLOR__ !== undefined) {
this.__CONTAINER__.fill(this.__COLOR__)
}

// Reset transform
this.__SET_POSITION__(this.transform.position)
this.__SET_ROTATION__(this.transform.rotation)
Expand All @@ -34,6 +45,10 @@ export class FCircle extends FPolygon {
this.emitOnLoaded()
}

__DRAW_SHAPE__(graphics: PIXI.Graphics): void {
graphics.circle(this.transform.position.x, this.transform.position.y, this.transform.scale.x * 100 / 2)
}

initCollider(options?: FColliderOptions): void {
super.initCollider({
...options,
Expand Down
Loading

0 comments on commit b496854

Please sign in to comment.