Skip to content

Commit

Permalink
Merge pull request #22 from jerzakm/sprite-offset-flip
Browse files Browse the repository at this point in the history
Sprite offset&flipX/Y
  • Loading branch information
jerzakm authored Dec 2, 2023
2 parents cce5d59 + 9071d46 commit f0e162c
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 18 deletions.
6 changes: 6 additions & 0 deletions .changeset/happy-boats-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@threejs-kit/instanced-sprite-mesh": minor
---

- flipY and flipX for sprites
- offset animations by time (random or set)
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,38 @@ title: Reference sheet
description: InstancedSpriteMesh
---

##
InstancedSpriteMesh extends troika's [InstancedUniformsMesh](https://protectwise.github.io/troika/three-instanced-uniforms-mesh/),
which in turn extends [THREE.InstancedMesh](https://threejs.org/docs/?q=instance#api/en/objects/InstancedMesh).

## Constructor
```ts
new InstancedSpriteMesh (
baseMaterial: T extends Material,
count: number,
spritesheet?: SpritesheetFormat,
options?: InstancedSpriteOptions
)
```

**baseMaterial** - Threejs material (recommended THREE.MeshBasicMaterial or THREE.MeshStandardMaterial) - more on this here -> (TODO)
**count** - number of instances
**spritesheet** - spritesheet metadata
**options** - (none atm, placeholder)


## Properties and Methods

### `.spritesheet`
set and get spritesheet (todo, test dynamic spritesheet setting on pre-built mesh)

### `.animation`
Set of methods to manipulate which animation is set
- **.animation.setAt( instanceId: number, animation: string )** - sets animation (by name) on instanceId
- **.animation.setGlobal( animation: string )** - sets default global animation that is used unless the `setAt` was called on the instance
- **.animation.resetInstances()** - removes all instance specific animation settings

### `.billboarding`
Set of methods to toggle billboarding
- **.billboarding.setAt( instanceId: number, enable: boolean )** - enables billboarding on instance
- **.billboarding.setGlobal( enable: boolean )** - sets default global billboarding state that is used unless the `setAt` was called on the instance
- **.billboarding.resetInstances()** - removes all instance specific billboarding settings
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { Image } from "astro:assets";
The `createSpritesheet` is a helper function that facilitates the creation of spritesheet metadata and a `Texture` which are essential for configuring
`InstancedSpriteMesh`. The process involves the following steps:

1. *Initialization:* Begin by invoking the `createSpritesheet` function. This function is the starting point for creating your spritesheet.
2. *Adding Images*: For each image you wish to include in the spritesheet, call the `add` function on the created spritesheet. This function requires specific parameters:
1. **Initialization:** Begin by invoking the `createSpritesheet` function. This function is the starting point for creating your spritesheet.
2. **Adding Images**: For each image you wish to include in the spritesheet, call the `add` function on the created spritesheet. This function requires specific parameters:
- `name`: A string identifier for the image. By default, this is set to "default".
- `imageUrl`: The URL or path to the image file.
- `config` object consiting of:
Expand All @@ -27,7 +27,7 @@ The `createSpritesheet` is a helper function that facilitates the creation of sp
h: number;
}
```
3. *Build*: After adding all of the animations, call `build()`. The `createSpritesheet` function returns a promise. This promise resolves to an object containing a `texture: THREE.Texture`, which can be applied as the map property of a material, and a `spritesheet` for use in the InstancedSpriteMesh.
3. **Build**: After adding all of the animations, call `build()`. The `createSpritesheet` function returns a promise. This promise resolves to an object containing a `texture: THREE.Texture`, which can be applied as the map property of a material, and a `spritesheet` for use in the InstancedSpriteMesh.


**Important Note:** The utility currently supports only the rowMajor layout. This means the frames in the spritesheet are ordered from left to right and top to bottom, progressing row by row. Adjustments to support different frame sequencing may be added in future updates.
Expand Down Expand Up @@ -68,11 +68,6 @@ import hit from "@assets/sprite/Monsters_Creatures_Fantasy/Flying_eye/Hit.png";

### example code

We call `createSpritesheet` and start defining metadata for each of the sprite files.
- first `add` a `Flight.png` file - we name the animation as `fly` and use `rowColumn` type. We then provide `w` of 8 - because there are 8 columns (frames left to right) and `h` of 1 - because there is only one row of animation
- for the second animation - `attack` - I'm going to use a `frameSize` type. So now we provide `w` and `h` in pixels of how big each frame is, then the builder calculates columsn and rows internally.
- Then I add `death` and `hit` animations, just like I did the first one. Notice that these animations have a different number of columns, but that's fine. Each of your animations can have different column and row numbers.

```ts

import { createSpritesheet } from '@threejs-kit/instanced-sprite-mesh';
Expand All @@ -99,4 +94,10 @@ const {spritesheet, texture} = await createSpritesheet()
h: 1
})
.build();
```
```

We call `createSpritesheet` and start defining metadata for each of the sprite files.
- first `add` a `Flight.png` file - we name the animation as `fly` and use `rowColumn` type. We then provide `w` of 8 - because there are 8 columns (frames left to right) and `h` of 1 - because there is only one row of animation
- for the second animation - `attack` - I'm going to use a `frameSize` type. So now we provide `w` and `h` in pixels of how big each frame is, then the builder calculates columsn and rows internally.
- Then I add `death` and `hit` animations, just like I did the first one. Notice that these animations have a different number of columns, but that's fine. Each of your animations can have different column and row numbers.

11 changes: 10 additions & 1 deletion apps/playground/src/routes/instanced-sprite/FlyerUpdater.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
<script lang="ts">
import { useFrame, watch, T, useThrelte } from '@threlte/core';
import { getContext } from 'svelte';
import { SphereGeometry, Vector2 } from 'three';
import {
BoxGeometry,
BoxHelper,
MeshBasicMaterial,
SphereGeometry,
SpotLightHelper,
Vector2
} from 'three';
const spriteCtx: any = getContext('instanced-sprite-ctx');
const { updatePosition, count, animationMap, setAnimation, mesh } = spriteCtx;
Expand All @@ -15,6 +22,8 @@
mesh.animation.setGlobal('fly');
mesh.offset.randomizeAll();
console.log(mesh);
const posX: number[] = new Array(count).fill(0);
Expand Down
4 changes: 2 additions & 2 deletions apps/playground/src/routes/instanced-sprite/Scene.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@

<slot />

<!-- <AnimatedInstancedSprite
<AnimatedInstancedSprite
textureUrl="/textures/sprites/player.png"
dataUrl="/textures/sprites/player.json"
fps={10}
loop={true}
{count}
>
<PlayerUpdater />
</AnimatedInstancedSprite> -->
</AnimatedInstancedSprite>

{#await spritesheet then { spritesheet, texture }}
<AnimatedInstancedSprite {spritesheet} {texture} fps={10} loop={true} {count}>
Expand Down
53 changes: 49 additions & 4 deletions packages/instanced-sprite-mesh/src/InstancedSpriteMesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,23 @@ export class InstancedSpriteMesh<
};
}

get offset() {
return {
setAt: (instanceId: number, offset: number) => {
this.setUniformAt("offset", instanceId, offset);
},
randomizeAll: (scalar: number = 1) => {
for (let i = 0; i < this.count; i++) {
// todo benchmark and optimize?
this.setUniformAt("offset", i, Math.random() * scalar);
}
},
unsetAll: () => {
this.unsetUniform("billboarding");
},
};
}

get loop() {
return {
setAt: (instanceId: number, loop: boolean) => {
Expand All @@ -111,6 +128,34 @@ export class InstancedSpriteMesh<
};
}

get flipX() {
return {
setAt: (instanceId: number, flipX: boolean) => {
this.setUniformAt("loop", instanceId, flipX ? 1 : 0);
},
setGlobal: (flipX: boolean) => {
this._spriteMaterial.uniforms.flipX.value = flipX ? 1 : 0;
},
unsetAll: () => {
this.unsetUniform("flipX");
},
};
}

get flipY() {
return {
setAt: (instanceId: number, flipY: boolean) => {
this.setUniformAt("flipY", instanceId, flipY ? 1 : 0);
},
setGlobal: (flipY: boolean) => {
this._spriteMaterial.uniforms.flipY.value = flipY ? 1 : 0;
},
unsetAll: () => {
this.unsetUniform("flipY");
},
};
}

play(animation: V, loop: boolean = true) {
return {
at: (instanceId: number) => {
Expand All @@ -125,7 +170,7 @@ export class InstancedSpriteMesh<
}

/** HSV shift tinting */
get tint() {
get hueShift() {
/**
* todo - reuse vector4 or something
*/
Expand All @@ -151,9 +196,9 @@ export class InstancedSpriteMesh<
}
this._spriteMaterial.uniforms.tint.value = tVector;
},
unsetAll: () => {
this.unsetUniform("tint");
},
// unsetAll: () => {
// this.unsetUniform("tint");
// },
};
}

Expand Down
19 changes: 18 additions & 1 deletion packages/instanced-sprite-mesh/src/material.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export const constructSpriteMaterial = (baseMaterial: Material): Material => {
startTime: { value: 0 },
/** per instance time offset, can be used so that all of the animations aren't perfectly synced */
offset: { value: 0 },
/** flip uvs on x */
flipX: { value: 0 },
/** flip uvs on y */
flipY: { value: 0 },
/**
* How many different animations there are.
* Needed to determine number of rows there are in DataTexture
Expand Down Expand Up @@ -110,6 +114,8 @@ export const constructSpriteMaterial = (baseMaterial: Material): Material => {
uniform float animationId;
uniform float startTime;
uniform float time;
uniform float flipX;
uniform float flipY;
uniform float offset;
uniform float fps;
uniform float loop;
Expand Down Expand Up @@ -149,7 +155,18 @@ export const constructSpriteMaterial = (baseMaterial: Material): Material => {
vec2 fSize = frameMeta.zw;
vec2 fOffset = vec2(frameMeta.xy);
vec2 spriteUv = fSize * vUv + fOffset;
vec2 transformedPlaneUv = vUv;
if(flipX == 1.){
transformedPlaneUv.x = 1. - transformedPlaneUv.x;
}
if(flipY == 1.){
transformedPlaneUv.y = 1. - transformedPlaneUv.y;
}
vec2 spriteUv = fSize * transformedPlaneUv + fOffset;
`;
fragmentShader = fragmentShader.replace(
`void main() {`,
Expand Down

1 comment on commit f0e162c

@vercel
Copy link

@vercel vercel bot commented on f0e162c Dec 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

threejs-kit-playground – ./apps/playground

threejs-kit-playground.vercel.app
threejs-kit-playground-git-main-jerzakm.vercel.app
threejs-kit-playground-jerzakm.vercel.app

Please sign in to comment.