Git Product home page Git Product logo

pixi-cull's Introduction

pixi-cull

A library to visibly cull objects designed to work with pixi.js (but not dependent on pixi.js).

Includes two types of culling algorithms: simple and spatial hash. The spatial hash may be also be used for collision detection, AI, etc.

Features include:

  • automatic calculate bounding boxes for pixi.js objects
  • also allow manual calculation for objects
  • bounds calculated from any viewport including pixi-viewport (pixi-viewport.getVisibleBounds())

Moving from v1 to v2

pixi-cull has been reworked and ported to typescript. The following functionality was removed:

  • removed the options to change the object's parameter names for AABB, dirty, spatial, and visible (this greatly simplified) the code
  • removed calculatePIXI as an option, since this library is now solely designed for pixi.js

Rationale

Since I maintain pixi-viewport, I was asked a number of times for a culling library. Well here it is. Choose from two drop-in algorithms to cull your objects.

Simple Example

import * as PIXI from "pixi.js"
import { Viewport } from "pixi-viewport"
import { Simple, SpatialHash } from "pixi-cull"

const app = new PIXI.Application()
document.body.appendChild(app.view)

// create viewport
const viewport = new Viewport({
  screenWidth: app.view.offsetWidth,
  screenHeight: app.view.offsetHeight,
  worldWidth: 10000,
  worldHeight: 10000,
})

app.stage.addChild(viewport)
viewport.drag().pinch().wheel().decelerate().moveCenter(5000, 5000)

// add red boxes
for (let i = 0; i < 500; i++) {
  const sprite = viewport.addChild(new PIXI.Sprite(PIXI.Texture.WHITE))
  sprite.tint = 0xff0000
  sprite.width = sprite.height = 100
  sprite.position.set(Math.random() * 10000, Math.random() * 10000)
}

const cull = new Simple() // new SpatialHash()
cull.addList(viewport.children)
cull.cull(viewport.getVisibleBounds())

// cull whenever the viewport moves
PIXI.Ticker.shared.add(() => {
  if (viewport.dirty) {
    cull.cull(viewport.getVisibleBounds())
    viewport.dirty = false
  }
})

Live Example

https://davidfig.github.io/pixi-cull/

API Documentation

https://davidfig.github.io/pixi-cull/jsdoc/

Installation

npm i pixi-cull

or grab the latest release and use it:

<script src="/directory-to-file/pixi.js"></script>
<script src="/directory-to-file/pixi-cull.min.js"></script>
<script>
  const SimpleCull = new Cull.Simple()
</script>

license

MIT License (c) 2021 YOPEY YOPEY LLC by David Figatner

pixi-cull's People

Contributors

adrienlemaire avatar davidfig avatar dependabot[bot] avatar justindujardin avatar likilee avatar maxakuru avatar richard-ejem avatar subnomo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pixi-cull's Issues

Sprites & BItmapTexts that are rotated are made invisible too soon and visible not early enough

http://nikkikoole.github.io/culltest/
https://github.com/NikkiKoole/nikkikoole.github.io/blob/master/culltest/main.js

Per sprite and bitmap text i also draw a rectangle for its bounds.
Those are culled correctly, but the sprites themselves arent.

I also tried it with the latest release from a day ago, also i am on pixi 5rc2 but see the same behaviour on 4.8.

Or am i just overlooking something obvious alltogether?

ps. when all the sprites and bitmaptexts arent rotated its working correctly.

edit:

just using the demo from you readme file:
add

sprite.rotation = Math.random() * Math.PI*2;

where the red boxes are generated
and you see the behaviour too:
dragging around you see some boxes being deleted from the view too soon.

Adding Types to pixi-cull

[tsserver 7016] [E] Could not find a declaration file for module 'pixi-cull'. /path/to/project/node_modules/pixi-cull/dist/index.js' implicitly has an 'any' type. Try npm i --save-dev @types/pixi-cullif it exists or add a new declaration (.d.ts) file containingdeclare module 'pixi-cull';`

DefinitelyTypes doesn't have have type file for pixi-cull. But it'd be better to have the types directly in this repo. Like pixi-viewport does.

Thanks a lot for making this. Very useful <3

Do I need to do something special when I resize the window?

I'm using this library, along with pixi-viewport, in an isometric game I'm building. I need to have a lot of tiles so optimizing rendering speed is important to me. I found that pixi-cull was very easy to set up and does exactly what I'm looking for. Great job!

I have run into one issue. When I resize the window, the culling logic's impression of the viewing area seems to be incorrect. It is culling objects when there is no need to.

Capture

I'm not rotating or skewing any sprites, so I don't think it's an incorrect calculation of the tiles' bounding boxes. I also find it weird that this only happens once I resize the window.

Here's the window's onresize handler:

        TileSystem.app.renderer.resize(TileSystem.app.view.clientWidth, TileSystem.app.view.clientHeight);
        this.viewport.resize(TileSystem.app.view.clientWidth, TileSystem.app.view.clientHeight);
        TileSystem.onResize();
        requestAnimationFrame(() => {
            if(this.cull != null)
                this.cull.cull(this.viewport.getVisibleBounds());
        });

I added the requestAnimationFrame on a whim that perhaps bounds calculation is not done until the next frame, but that didn't help.

I also tried the pixi-cull live demo, but when I resize the window, the canvas just gets scaled and the viewport doesn't seem to resize.

I've tried both algorithms; they both have the same symptoms when the window resizes.

Any suggestions? The only workaround I can think of would be for me to disable culling once the window resizes, which will probably make it unusable on mobile devices.

Support Pixi v7

Hi, I am trying to install this package with the new PixiJs v7.0.4. But the npm won't let me due to the Pixi version conflict.
Can we update the Pixi dependency here to v7?

Import without Node.js

First of all, thanks for the library! Really important for optimization so thanks for putting this together and maintaining.

I started setting this up last night following the code snippet in the README and it's not quite working as presented for non Node.js users like myself.

README setup tutorial:

or grab the latest release and use it:
<script src="/directory-to-file/pixi.js"></script>
<script src="/directory-to-file/pixi-cull.min.js"></script>
<script>
    var SimpleCull = new PIXI.extras.Cull.Simple();
</script>

โ˜๏ธ the latest release link in README maps to pixi-viewport, not the releases for pixi-cull btw

Running this setup, it can't find Cull in PIXI.extras. In fact, the PIXI object does not have extras. I can't see anywhere in the source where this gets added to PIXI.extras - it looks like it just creates classes Simple and SpatialHash in the global scope, so I tried calling those. Constructing Simple using var viewFrustumCuller = new Simple(); also results in an error:

Uncaught ReferenceError: Simple is not defined
    at setupUI (pixibox.js:61)
    at t.value (mini-signals.js:93)
    at e._onComplete (resource-loader.esm.js:2233)
    at resource-loader.esm.js:2271

To get this to load I had to rip out all of the top-level export wrappers in the pixi-cull.js build. It now loads but not sure if it's working yet.

So my question here is: is this a bug or am I setting this up incorrectly?

Mirroring #3 and reopening because issue still remains. Perhaps it's just a matter of updating the docs for ease of understanding how to set this up. Would be great if this was namespace scoped like other pixi libraries: for example, pixi-filters when I include pixi-filters.js I can access everything with PIXI.filters.GlowFilter.

Using latest version of PIXI: 6.0.0-rc.3 on WebGL 2

Performance degraded with higher number of culled elements

Hi,

when running examples with higher number of culled squares (> 500k) performance drops. Since, with culling, GPU should only render visible elements I am wondering why it comes to these performance drops? Shouldn't performance drops correlate with number of visible elements rather than culled elements? Is this an issue or I am missing something?

Thanks in advance!

Testing code:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Hello World</title>
</head>
  <script src="pixi/pixi.min.js"></script>
  <script src="pixi/simple.js"></script>
  <script src="pixi/spatial-hash.js"></script>
  <script src="pixi/viewport.js"></script>
<body>
  <script type="text/javascript">
    var app = new PIXI.Application();
    document.body.appendChild(app.view);

    // create viewport
    var viewport = new Viewport.Viewport({
        screenWidth: app.view.offsetWidth,
        screenHeight: app.view.offsetHeight,
        worldWidth: 10000,
        worldHeight: 10000
    });

    app.stage.addChild(viewport);
    viewport.drag().pinch().wheel().decelerate().moveCenter(5000, 5000);

    // add red boxes
    for (var i = 0; i < 200000; i++)
    {
        var sprite = viewport.addChild(new PIXI.Sprite(PIXI.Texture.WHITE));
        sprite.tint = 0xff0000;
        sprite.width = sprite.height = 20
        sprite.position.set(Math.random() * 100000, Math.random() * 100000);
    }

    var cull = new Simple();
    cull.addList(viewport.children);
    cull.cull(viewport.getVisibleBounds());

    // cull whenever the viewport moves
    PIXI.ticker.shared.add(() =>
    {
        if (viewport.dirty)
        {
            cull.cull(viewport.getVisibleBounds());
            viewport.dirty = false;
            console.log(cull.stats());
        }
    });
  </script>
</body>
</html>

Example using SpatialHash?

Hi there. Given the large number of objects I am testing this with, it's very clear to me that I need to cull objects in chunks, since the process of culling alone tanks performance quite a bit.

I can't figure out how to use SpatialHash though. Is there any example you could share, or a test you have laying around of it? Or some use instructions at least.

I'm currently getting errors like not having containers (but if I have to create containers myself, what's the difference than using the regular SimpleHash and passing containers to that instead?).

Any clarification would be appreciated.

Thank you for your work.

Cannot read property 'Cull' of undefined

I followed the Installation example:
`

<script src="/directory-to-file/pixi.js"></script> <script src="/directory-to-file/pixi-cull.min.js"></script> <script> var SimpleCull = new PIXI.extras.Cull.Simple(); </script>

`

and I'm getting an error: Cannot read property 'Cull' of undefined

Not too sure how to add it

Hey, sorry i'm a bit nub but I tried to add it to my project by adding pixi-cull.js but I cant see it available in PIXI.extras

Also the link to releases on your readme.md goes to pixi-viewport.

How to handle local coordinates?

Hi, thanks for creating this library, it's awesome.
I encountered an issue is that my coordinate system is based on container, so the structure will be like
image

the coordinate of my graphics object is a local coordinate(relative to the parent container), and I wanna do culling so I add the children of the container to cull object, it will be like

const collectChildren = (viewport: Viewport) => {
  return _flatten((viewport.children as Container[]).map(({ children }) => children));
};

cull.addList(collectChildren(viewport));
cull.cull(viewport.getVisibleBounds());

seems the cull method is to use the x and y of the object directly instead of worldTransforms.x to do a comparison, it will cull some objects that are still in my view unexpectedly, is there any direction I can try?

SpatialHash loses followed sprite and is always dirty

Video demonstrating the issues: https://youtu.be/RPK06B6LeMo

Related code:
const Viewport: React.FC<IProps> = ({ worldWidth, worldHeight, children }) => {

  // Create a new pixi-viewport instance
  const viewport = useMemo(() => {
    const newViewport = new PixiViewport({
      // the interaction module is important for wheel to work properly when
      // renderer.view is placed or scaled
      interaction: app.renderer.plugins.interaction,
      // FIXME: The viewport breaks if trying to use innerWidth/innerHeight
      screenHeight: document.documentElement.clientHeight,
      screenWidth: document.documentElement.clientWidth,
      ticker: app.ticker,
      worldHeight,
      worldWidth,
    });

    // worldWidth and worldHeight are originally calculated from the tiled map as follows
    // worldWidth = tilewidth * width, worldHeight = tileheight * width
    const fitHeight = newViewport.findFitHeight(worldHeight);
    const fitWidth = newViewport.findFitWidth(worldWidth);
    const scale = Math.min(fitHeight, fitWidth);
    setMinScale(scale);

    newViewport
      // .clamp({ direction: "all" }) // stop the viewport from moving beyond your boundaries.
      .setZoom(0.1)
      .snap(worldWidth / 2, worldHeight / 2, { time: 0 })
      .animate({
        // also works with snapZoom
        // setZoom and fitWorld work without easing
        callbackOnComplete: () => setAnimationCompleted(true),
        ease: "easeInOutSine",
        scale,
        time: DELAY_MAP,
      })
      .wheel()
      .decelerate();

    return newViewport;
  }, [app, worldWidth, worldHeight]);

  // Reactify pixi-viewport
  const ViewportComponent = useMemo(
    () => PixiComponent("Viewport", { create: () => viewport }),
    [viewport]
  );

  // cutting offscreen shapes
  const cull = useMemo(() => {
    // const newCull = new Cull.Simple({dirtyTest: false});
    // newCull.addList(viewport.children);
    const newCull = new Cull.SpatialHash({
      dirtyTest: true,
    });
    newCull.addContainer(viewport);
    newCull.cull(viewport.getVisibleBounds());
    return newCull;
  }, [viewport]);

  // cull whenever the viewport moves
  let notDirty = 0;
  useTick(() => {
    if (viewport.dirty) {
      notDirty = 0;
      cull.cull(viewport.getVisibleBounds());
      const { total, culled } = cull.stats();
      CullPanel.update(culled, total);
      viewport.dirty = false;
    } else notDirty += 1;
    if (notDirty > 1) console.log("viewport tick not dirty");
  });

  useEffect(() => {
    // Trigger the character follow once the map animation is completed
    if (animationCompleted && characterRef.current) {
      const sprite = characterRef.current;
      viewport
        .animate({
          callbackOnComplete: () => {
            viewport.follow(sprite.parent, { acceleration: 1 });

            // At this point, all tiles should be available in the world, so we
            // start sorting the sprites inside the Viewport every frame
            app.ticker.add(sortViewport);
          },
          position: sprite.parent.position,
          scale: 1,
          time: DELAY_ZOOM,
        })
        .pinch() // Allow zooming out on mobiles with 2 fingers
        // maxScale > 1 will display seams around tiles
        .clampZoom({ maxScale: 2, minScale });
    }
  }, [viewport, animationCompleted, minScale, app.ticker, sortViewport]);

  return <ViewportComponent>{children}</ViewportComponent>;

Since I'm working on a huge map with 20000+ sprites, cull.Simple isn't enough (FPS drops from 60 to ~20 when not moving).

First issue: When changing the culling from Simple to SpatialHash, the sprite that is followed by viewport is culled out when (I guess) it gets out of the default 1000x1000 cull cell.

Second issue: Whenever I call cull.cull() && viewport.dirty = false, the next tick is false, and the following one is always back to true. When I'm not moving and nothing is moving on the screen, I'm expecting the viewport.dirty flag to stay false. Note that if I press on the screen and keep the followed sprite's velocity to 0, viewport.dirty do stays false as long as my finger is pressing the screen.

Do you have suggestions on how to resolve these issues ?

Cannot find name 'viewport' with ES6 import

I'm trying to use this library with the (excellent) pixi-viewport, with the following code:

import * as PIXI from "pixi.js";
import { Viewport as PixiViewport } from "pixi-viewport";
import Cull from "pixi-cull";

I get this error:

TypeScript error in /work/pixi-project/src/game/Game.ts(75,18):
Cannot find name 'viewport'.  TS2304

    73 | 
    74 |     const cull = new Cull.Simple();
  > 75 |     cull.addList(viewport.children);
       |                  ^
    76 |     cull.cull(viewport.getVisibleBounds());
    77 | 
    78 |     window.maps = [];

I believe it's because of my use of import instead of require, the documentation doesn't provide a workaround for import, is it possible to use it currently?

Culling seems not working

Hi, I created a map with pixi.js and this plugin.
As the picture shown I create lots of points, each point is PIXI.Container (some sprites inside it )
image

According to the document, the less points shown, the higher the map rendering performance. but when I zoomed the map and let less points shown, I found that rendering performance has not improved (the FPS is low). I also log the culling
image

I believe this is a problem with my usage. The following is my code:

  const cull = new Simple({ calculatePIXI: true });
  cull.addList(viewport.children);
  cull.cull(viewport.getVisibleBounds());

  const callRender = () => {
    if (viewport.dirty) {
      cull.cull(viewport.getVisibleBounds());
      viewport.dirty = false;
      console.log(cull.stats());
    }
    renderer.render(viewport);
  };
  viewport.on('moved', callRender);

the function callRender is used for re-render map manually.

Could you please help me resolve this?

Cannot read property 'x' of undefined

Hi, currently I'm getting started with pixi and your plugins.
I tried to get your example up and running, but I can't get this to work

Pixi v4.8.5 + culling + viewport
game.js is a combined js with pixi.js + bundle version of cull and viewport

var app = new PIXI.Application();
document.body.appendChild( app.view );

// create viewport
var viewport = new PIXI.extras.Viewport( {
    screenWidth : window.innerWidth,
    screenHeight : window.innerHeight,
    worldWidth : 10000,
    worldHeight : 10000
} );

app.stage.addChild( viewport );
viewport.drag().pinch().wheel().decelerate();

// add red boxes
for( var i = 0; i < 1; i++ ) {
    var sprite = viewport.addChild( new PIXI.Sprite( PIXI.Texture.WHITE ) );
    sprite.tint = 0xff0000;
    sprite.width = sprite.height = 100;
    sprite.position.set( Math.random() * 10000, Math.random() * 10000 );
}

var cull = new PIXI.extras.Cull.Simple();
cull.addList( viewport.children );
cull.cull( viewport.getVisibleBounds() );

getting the error

game.js:45938 Uncaught TypeError: Cannot read property 'x' of undefined
    at Simple.cull (game.js:45938)
    at game.js:46990
cull @ game.js:45938
(anonymous) @ game.js:46990

-->

object[this.visible] = box.x + box.width > bounds.x && box.x < bounds.x + bounds.width && box.y + box.height > bounds.y && box.y < bounds.y + bounds.height;

thanx for your help

TypeError: Cannot read property 'x' of undefined

Version:
pixi-cull: 0.6.0
pixi-viewport: 4.9.2
pixi.js: 5.2.1

Code

const cull = new Cull.Simple({ calculatePIXI: true });
cull.addList(viewport.children);
cull.cull(viewport.getVisibleBounds());

Error Stack

TypeError: Cannot read property 'x' of undefined
    at Simple.cull (simple.js?081c:154)
    at callRender (pixi-initialization.js?e625:78)
    at centerView (pixi-initialization.js?e625:128)
    at eval (Simulator.vue?96ba:972)
    at eval (service.js?67cf:86)

[Feature] cull() callback

After culling non-visible sprites, I do other operations like calling stats() and queryCallback().

  // cull whenever the viewport moves
  useTick(() => {
    if (viewport.dirty) {
      const AABB = viewport.getVisibleBounds();
      cull.cull(AABB);
      // Also show/hide debug boxes on visible sprites
      cull.queryCallback(AABB, (sprite) => {
        if (sprite.children.length > 1) {
          const boxes = sprite.children[sprite.children.length - 1];
          if (boxes.visible !== showSpriteBoxes)
            boxes.visible = showSpriteBoxes;
        }
        return false;
      });
      // Update stats.js panel info
      const { total, culled } = cull.stats();
      CullPanel.update(culled, total);
      viewport.dirty = false;
    }
  });

When reading the code, I realize that the O(n^2) double-for loop operation is repeated 3 times (in cull(), queryCallback() and stats()).

Could we have a callback option to cull(), and maybe return stats including lastBuckets ?
The children would be looped only once in that case, and my code would look like:

  // cull whenever the viewport moves
  useTick(() => {
    if (viewport.dirty) {
      const { total, culled } = cull.cull(
        viewport.getVisibleBounds(),
        // Also show/hide debug boxes on visible sprites
        (sprite) => {
          if (sprite.children.length > 1) {
            const boxes = sprite.children[sprite.children.length - 1];
            if (boxes.visible !== showSpriteBoxes)
              boxes.visible = showSpriteBoxes;
          }
          return false;
        }
      )
      // Update stats.js panel info
      CullPanel.update(culled, total);
      viewport.dirty = false;
    }
  });

Some Sprites could disappeared when init out of screen in the beginning

As stated in the title, If I put some sprites out of the viewport screen in the beginning of rendering, these sprites could disappeared when wheel the viewport for view them. But not all sprites disappear, it seems to be random

Case 1
image

Case 2
image

The complete map looks like this
image

Even though update the cull to the latest version

version

image

The following is my code:
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.