What Is It?
Regions are a common data structure in 2-D graphics systems. They let you easily describe non-rectangular sections of the screen by combining rectangles together — if you've ever said, "I need a way to describe this area and this area, but without that area," you're describing a region!
This library implements full 2-D regions in JavaScript. You can create
regions by unioning rectangles together, and you can subtract and intersect
other rectangles and regions to make more complex regions. You can convert
the resulting region to a set of nonoverlapping rectangles (for example, to
create <div>
elements that describe the region), and you
can test the region to find out when other regions or a point (like the
mouse!) intersect it.
This library is designed to be very efficient, and it can handle many complicated operations quickly — fast enough for animation! Scroll down and play with the live demo to see just how fast and flexible this library really is.
Features
- Set-theoretic operations: Union, Intersect, Subtract, Exclusive-or, Logical Not
- Intersection tests, for other regions and for points
- Equality tests, and efficient hash-code generation for dictionary/map keys
- Conversion to and from sets of rectangles
- Only 4 KB minified and gzipped
- Transformations: Translate and Scale
- Infinite and empty regions
- Conversion to a path (set of polygons)
- Data structures that are both opaque and immutable (i.e., safe)
- Thoroughly unit-tested, with 98% coverage
Installation
NodeJS and NPM
In NodeJS or a CommonJS environment, install the region2d package, and then use import
or require
.
First:
npm install --save region2d
Then:
import Region2D from "region2d";
(ES6)
or
import { Region1D, Region2D, RegionError } from "region2d";
(ES6)
or
var Region2D = require('region2d');
(in classic JavaScript)
Vanilla JavaScript for the Browser
- Download a copy of
region2d.js
orregion2d.min.js
. This plain-JavaScript bundle includes both Region types.
The minified bundle is 13 KB uncompressed, and is only about 4 KB gzipped. - Include
<script src="region2d.js"></script>
or<script src="region2d.min.js"></script>
in your page.
This will introduce three new global types,Region1D
,Region2D
, andRegionError
.
Alternatively, you can use a copy hosted on the unpkg content-delivery network (CDN):
- Include
<script src="https://unpkg.com/region2d@1.0.0/plain/region2d.js"></script>
or<script src="https://unpkg.com/region2d@1.0.0/plain/region2d.min.js"></script>
in your page.
This will introduce three new global types,Region1D
,Region2D
, andRegionError
.
Live Demo
Try it! In the sandbox below, you can drag or resize any of the rectangles you see. On the right, you can create new rectangles or delete existing rectangles to see how they are turned into regions.
By the way, the demo above does not use <canvas> — Region2D calculates fast enough that those rectangles — and the "interior" and "exterior" decompositions — are able to all be real <div> elements!
Usage
Creating Regions
Region2D
is designed to be very easy to use and very flexible. Here's
a simple demonstration of creating a region from a rectangle:
import Region2D from 'region2d'; var myRegion = new Region2D({ x:5, y:10, width:10, height:20 });
There are several ways to construct a Region2D
from a rectangle:
- You can create a
Region2D
from an array containing a set of exactly four numbers, which will be interpreted as the rectangle's leftmostx1
, topmosty1
, its rightmostx2
, and its bottommosty2
, in that order. - You can create a
Region2D
from any object with numericx
,y
,width
, andheight
properties. - You can create a
Region2D
from any object with numericleft
,top
,right
, andbottom
properties. - In browser environments, you can pass in an
HTMLElement
instance (a DOM element) to construct a region from its bounding box (page-relative).
Other Ways to Create Regions
- There is a helper
Region2D.fromRects()
method. This is passed an array of rectangles, which will be unioned together to form the resulting region. - There is a helper
Region2D.fromRawRows()
method that allows you to create the region data directly (fast, but for advanced use only!) - Also, there are static
Region2D.empty
andRegion2D.infinite
regions, so that you don't need to construct those yourself.
Combining Regions
Once you have created a few Region2D
objects, you can combine them together
using standard constructive-solid-geometry operations:
var myRegion = new Region2D({ x:5, y:10, width:10, height:20 }); var yourRegion = new Region2D({ x:10, y:5, width:20, height:10 }); var theirRegion = new Region2D([ 15, 15, 20, 20 ]); var ourRegion = myRegion.union(yourRegion); var remainingRegion = ourRegion.subtract(theirRegion);
Many common CSG primitives are supported, including .union()
, .intersect()
,
.subtract()
, and .xor()
(exclusive-or). In addition, you can also use the
.not()
method to obtain the complement of a region. The Region2D library fully supports
infinite regions, so logical-not works the way you think it should!
Transforming Regions
There are methods you can use to directly alter the size and position of a region:
.scale(scaleX, scaleY)
- Scale this region: Multiply each of its coordinates by the given scaling factors (1.0 = no change, 2.0 = double size, and so on)..translate(deltaX, deltaY)
- Translate this region: Add the given delta values to each of its coordinates (0.0 = no change, 1.0 = move one unit to the right, and so on)..transform(scaleX, scaleY, deltaX, deltaY)
- Scale and then translate this region: This is the same as invoking each operation in sequence, only faster.
There's no explicit .clip()
method: If you need to "clip" a region to a rectangle,
you can simply .intersect()
it with another region created from that rectangle.
Testing Regions
There are lots of tests you can perform against regions to determine things about them, including:
.isEmpty()
- Determine if a region is empty (has no rectangles).isInfinite()
- Determine if a region stretches to infinity in any direction.isFinite()
- Opposite of.isInfinite()
.isRectangular()
- Determine if a region consists of exactly one rectangle.doesIntersect(otherRegion)
- Determine if two regions intersect.relate(otherRegion)
- Determine how two regions intersect: disjoint, A-contains-B, B-contains-A, or equal..isPointIn(x, y)
- Determine if the given point is contained within this region.equals(otherRegion)
- Determine whether two regions are equal (equivalent sets).
Data Extraction
While it's useful to be able to create regions from rectangles, there are also lots of ways to get rectangle data back out of the regions too:
.getRects()
- Get a minimal array of nonoverlapping rectangles that exactly describe this region.getCount()
- Quickly get the number of rectangles that.getRects()
would return.getBounds()
- Get the smallest rectangle that contains this entire region..getPath()
- Generate a set of one or more clockwise polygons that describe this region's boundary..getHashCode()
- Get a 32-bit "hash" code for this region, useful for quickly comparing two regions for inequality..getRawRows()
- Get a copy of the raw row data of this region (fast, but for advanced use only!)
License
Copyright © 2017 by Sean Werkema.
This software is licensed under the terms of the Apache 2.0 Open-Source License, which basically means:
- It's free, and you can —
- Do what you want with it, but —
- Don't claim you wrote it, and —
- Don't complain if it breaks.
Questions, Comments, and Bugs
Questions? Comments? Found a bug? Want a feature? Please use the issue tracker for Region2D on GitHub.