initial release
53
.eslintrc.json
Executable file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react",
|
||||
"flowtype"
|
||||
],
|
||||
"globals": {
|
||||
"ReactClass": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:flowtype/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"settings": {
|
||||
"flowtype": {
|
||||
"onlyFilesWithFlowAnnotation": false
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"strict": 0,
|
||||
"flowtype/define-flow-type": 1,
|
||||
"flowtype/use-flow-type": 1,
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
"no-console": [
|
||||
2,
|
||||
{ "allow": ["warn"] }
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
||||
9
.github/ISSUE_TEMPLATE.md
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
### *feature request* or *bug report*?
|
||||
|
||||
<!-- If you report a bug: -->
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Steps to reproduce the behavior
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. The two fields below are mandatory. -->
|
||||
|
||||
**Summary**
|
||||
|
||||
<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->
|
||||
|
||||
**Test plan**
|
||||
|
||||
<!-- Demonstrate the code is solid. Please provide if possible an example and also make sure your changes is covered by tests -->
|
||||
11
.gitignore
vendored
Executable file
@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
coverage
|
||||
build
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log
|
||||
*.tmp
|
||||
packages/*/lib
|
||||
packages/gl-react/gl-react.js
|
||||
packages/gl-react-dom/gl-react-dom.js
|
||||
tests/node_modules_to_test/
|
||||
67
CONTRIBUTING.md
Executable file
@ -0,0 +1,67 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
Contributions are always welcome, no matter how large or small.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
git clone https://github.com/gre/gl-react.git
|
||||
npm run install-all
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
You must run this each time you modify one of the packages/ of the library.
|
||||
It rebuild the lib/ folders and will copy the packages into the projects.
|
||||
|
||||
## Testing
|
||||
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
if you need to regenerate the snapshots:
|
||||
|
||||
```sh
|
||||
npm run test-rewrite-snapshots
|
||||
```
|
||||
|
||||
typecheck:
|
||||
|
||||
```sh
|
||||
npm run flow
|
||||
```
|
||||
|
||||
Finally, Please check that ALL examples of the cookbook are working correctly.
|
||||
|
||||
## Cookbook
|
||||
|
||||
**Run it**
|
||||
|
||||
```sh
|
||||
cd cookbook
|
||||
npm start
|
||||
```
|
||||
|
||||
**Deploy** (only `gre` can do this at the moment!)
|
||||
|
||||
```sh
|
||||
npm run deploy-cookbook
|
||||
```
|
||||
|
||||
## Clean up and reinstall everything
|
||||
|
||||
```sh
|
||||
npm run clean-all
|
||||
npm run install-all
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
By contributing to gl-react, you agree that your contributions will be licensed
|
||||
under its [MIT license](LICENSE).
|
||||
19
LICENSE
Executable file
@ -0,0 +1,19 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2016 <renaudeau.gaetan@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
130
README.md
Executable file
@ -0,0 +1,130 @@
|
||||
> **NB:** This is gl-react v3. For gl-react v2, please see [ProjectSeptemberInc/gl-react](https://github.com/ProjectSeptemberInc/gl-react).
|
||||
|
||||
> [gre/gl-react](https://github.com/gre/gl-react) repository is a complete rewrite of [ProjectSeptemberInc/gl-react](https://github.com/ProjectSeptemberInc/gl-react) library (gl-react v2).
|
||||
It plans to be the gl-react v3.
|
||||
We keep both repository at same time because (1) this work is not yet finished and (2) this repository is now a "multi libraries" repository (it's just easier to maintain that way).
|
||||
|
||||
## v3 alpha: development in progress
|
||||
|
||||
- [x] gl-react, universal implementation
|
||||
- [x] gl-react-dom, DOM implementation
|
||||
- [x] gl-react-headless, Node.js implementation
|
||||
- [x] tests: 100% test coverage!
|
||||
- [ ] gl-react-native, React Native implementation
|
||||
|
||||
**The main remaining work of v3 is the React Native implementation**:
|
||||
|
||||
The long term plan is to rely on [Exponent's WebGL implementation](https://docs.getexponent.com/versions/v11.0.0/sdk/gl-view.html) to implement the WebGL layer. The implementation is still very young and experimental (only implement a subset of WebGL), but as soon as this implementation guarantees a good conformance, the library should just work! **I encourage everyone to contribute to make Exponent WebGL implementation robust**, independently from the library you use at the end (Three.js / Pixi.js / regl / gl-react / whatever!).
|
||||
|
||||
Here is the parts we would like to focus on solving in that implementation:
|
||||
- Support for framebuffers. This is fundamental for gl-react.
|
||||
- interoperability with React Native Image `source` prop (basically should support same format as a input for `gl.texImage2D`)
|
||||
- The WebGL implementation should be a standalone implementation that we can depend as a library (shouldn't requires you to use Exponent if you just use React Native).
|
||||
- interoperability with more "pixel sources" (Video, Camera, ...)
|
||||
|
||||
|
||||
**Other remaining topics:**
|
||||
- Flow type support: we are waiting the next flow version that should bring WebGL type support: https://github.com/facebook/flow/pull/2764 .
|
||||
|
||||
<img width="32" alt="icon" src="https://cloud.githubusercontent.com/assets/211411/9813786/eacfcc24-5888-11e5-8f9b-5a907a2cbb21.png"> gl-react
|
||||
========
|
||||
|
||||
`gl-react` is a [React](https://facebook.github.io/react/) library to write and compose WebGL shaders. *Implement complex effects by composing React components.*
|
||||
|
||||
This universal library must be coupled with one of the concrete implementations:
|
||||
|
||||
- [`gl-react-dom`](packages/gl-react-dom/) for React DOM (web using WebGL).
|
||||
- **unfinished** [`gl-react-native`](packages/gl-react-native/) for React Native (iOS/Android via OpenGL).
|
||||
- [`gl-react-headless`](packages/gl-react-headless/) for Node.js (used for testing for now)
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
- [Cookbook, examples, API](https://gl-react-cookbook.surge.sh)
|
||||
- Chat [#gl-react on reactiflux](https://discordapp.com/channels/102860784329052160/106102146109325312)
|
||||
|
||||
|
||||
## Gist
|
||||
|
||||
```js
|
||||
import React from "react";
|
||||
import { Shaders, Node, GLSL } from "gl-react";
|
||||
const shaders = Shaders.create({
|
||||
helloBlue: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform float blue;
|
||||
void main() {
|
||||
gl_FragColor = vec4(uv.x, uv.y, blue, 1.0);
|
||||
}`
|
||||
}
|
||||
});
|
||||
class HelloBlue extends React.Component {
|
||||
render() {
|
||||
const { blue } = this.props;
|
||||
return <Node
|
||||
shader={shaders.helloBlue}
|
||||
uniforms={{ blue }}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
import the correct implementation,
|
||||
|
||||
```js
|
||||
import {Surface} from "gl-react-dom"; // for React DOM
|
||||
import {Surface} from "gl-react-native"; // for React Native
|
||||
import {Surface} from "gl-react-headless"; // for Node.js!
|
||||
```
|
||||
|
||||
and this code...
|
||||
|
||||
```js
|
||||
<Surface width={300} height={300}>
|
||||
<HelloBlue blue={0.5} />
|
||||
</Surface>
|
||||
```
|
||||
|
||||
...renders:
|
||||
|
||||

|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **React, VDOM and immutable paradigm**: OpenGL is a low level imperative and mutable API. This library takes the best of it and exposes it in an immutable, descriptive way with React.
|
||||
- **React lifecycle** allows partial GL re-rendering. Only a React Component update will trigger a redraw. Each Node holds a framebuffer state that get redrawn when component updates, then scheduling a Surface reflow.
|
||||
- **Developer experience**
|
||||
- React DevTools works like on DOM and allows you to inspect and debug your stack of effects.
|
||||
- **Uniform bindings**: bindings from JavaScript objects to OpenGL GLSL language types (bool, int, float, vec2, vec3, vec4, mat2, mat3, mat4, sampler2D...)
|
||||
- An **extensible texture loader** that allows to support any content that goes in the shader as a sampler2D texture.
|
||||
- support for images
|
||||
- support for videos (currently `gl-react-dom`)
|
||||
- support for canvas (`gl-react-dom`)
|
||||
- **flowtype** support.
|
||||
- headless tests with Jest. We have reached 99.9% test coverage!
|
||||
- Modular, Composable, Sharable. Write shaders once into components that you re-use everywhere! At the end, users don't need to write shaders.
|
||||
|
||||
## Atom nice GLSL highlighting
|
||||
|
||||
If you are using Atom Editor, you can have JS inlined GLSL syntax highlighted.
|
||||
|
||||

|
||||
|
||||
**To configure this:**
|
||||
|
||||
- add `language-babel` package.
|
||||
- Configure `language-babel` to add `GLSL:source.glsl` in settings "*JavaScript Tagged Template Literal Grammar Extensions*".
|
||||
- (Bonus) Add this CSS to your *Atom > Stylesheet*:
|
||||
```css
|
||||
/* language-babel blocks */
|
||||
atom-text-editor::shadow .line .ttl-grammar {
|
||||
/* NB: designed for dark theme. can be customized */
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
}
|
||||
atom-text-editor::shadow .line .ttl-grammar:first-child:last-child {
|
||||
display: block; /* force background to take full width only if ttl-grammar is alone in the line. */
|
||||
}
|
||||
```
|
||||
38
TRADEOFFS.md
Executable file
@ -0,0 +1,38 @@
|
||||
# gl-react tradeoffs
|
||||
|
||||
## "WebGL is a 2D API"
|
||||
|
||||
The library is focused on composing fragment shaders for use-cases like 2D effects on images/videos/...
|
||||
**That means Vertex shader and Vertex data are currently out of scope, `gl-react` isn't for 3D use-cases yet**. We might provide soon a escape hatch to do arbitrary gl calls in a Node.
|
||||
|
||||
## Framebuffers (FBOs)
|
||||
|
||||
The library uses **one framebuffer per `<Node>`** and do not re-use FBOs across Node instances.
|
||||
This allows to implement Node caching (only redraw Node if necessary).
|
||||
|
||||
2 exceptions:
|
||||
|
||||
- The root `<Node>` do not uses any FBO because it directly draws on the `<Surface>` canvas.
|
||||
- The use of `backbuffering` will make a `<Node>` uses 2 FBOs: the framebuffer and the backbuffer, each draw will make them swap.
|
||||
|
||||
## Surface and Node redraw
|
||||
|
||||
In order to make redraw efficient, `gl-react` have 2 phases: the `redraw()` phase and the `flush()` phase (reflecting the respective methods available both on `Surface` and `Node`). In short:
|
||||
|
||||
- **redraw() phase** sets a dirty flag to a Node and all its "dependents" (other nodes, buses, surface). *redraws happen generally bottom-up to the Surface.*
|
||||
- **flush() phase** draws all nodes that have the redraw flag. *draws happens top-down from the Surface.*
|
||||
|
||||
`redraw()` is directly hooked to React update lifecycle (re-rendering a Node will calls `redraw()` for you).
|
||||
To make this system efficient, **the flush() is by default asynchronous**, i.e. `redraw()` means scheduling a new gl draw.
|
||||
Surface have a main loop that runs at 60 fps and call `flush()`. This is very efficient because if Surface does not have the redraw flag, `flush()` does nothing.
|
||||
|
||||
> If you want to make a `<Node>` synchronously flushing the drawing each time it renders, you can still use the `sync` prop (see in the example "`GameOfLife`").
|
||||
|
||||
## `<Bus>`, a way to share computation
|
||||
|
||||
[gl-react used to automatically factorize the duplicates elements of your tree](http://greweb.me/2016/06/glreactconf/), **it has been decided to remove this feature**
|
||||
in order to make you fully in control.
|
||||
*This was actually a pain to implement it right, a premature optimization that can have some slower performance.*
|
||||
|
||||
The new gl-react embraces more the React paradigm.
|
||||
There is a new equivalent way to express a Graph (and share computation): **using a `<Bus>`**.
|
||||
8
cookbook-rn/.babelrc
Executable file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"presets": ["react-native-stage-0/decorator-support"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["transform-react-jsx-source"]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
cookbook-rn/.gitignore
vendored
Executable file
@ -0,0 +1,3 @@
|
||||
node_modules/**/*
|
||||
.exponent/*
|
||||
npm-debug.*
|
||||
BIN
cookbook-rn/5EOyTDQ.jpg
Executable file
|
After Width: | Height: | Size: 155 KiB |
28
cookbook-rn/HelloGL.js
Executable file
@ -0,0 +1,28 @@
|
||||
//@flow
|
||||
import React, {
|
||||
Component,
|
||||
} from "react";
|
||||
import {GLSL, Node, Shaders} from "gl-react";
|
||||
import {Surface} from "gl-react-native";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
helloGL: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
void main() {
|
||||
gl_FragColor = vec4(uv.x, uv.y, 0.5, 1.0);
|
||||
}
|
||||
`
|
||||
}
|
||||
});
|
||||
|
||||
export default class GLExample1 extends Component {
|
||||
render () {
|
||||
return (
|
||||
<Surface width={100} height={100}>
|
||||
<Node shader={shaders.helloGL} />
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
}
|
||||
34
cookbook-rn/HelloTexture.js
Executable file
@ -0,0 +1,34 @@
|
||||
//@flow
|
||||
import React, {
|
||||
Component,
|
||||
} from "react";
|
||||
import {GLSL, Node, Shaders} from "gl-react";
|
||||
import {Surface} from "gl-react-native";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
helloTexture: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
void main() {
|
||||
gl_FragColor = texture2D(t, uv);
|
||||
}
|
||||
`
|
||||
}
|
||||
});
|
||||
|
||||
export default class GLExample1 extends Component {
|
||||
render () {
|
||||
return (
|
||||
<Surface width={100} height={100}>
|
||||
<Node
|
||||
shader={shaders.helloTexture}
|
||||
uniforms={{
|
||||
t: require("./5EOyTDQ.jpg"),
|
||||
}}
|
||||
/>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
}
|
||||
21
cookbook-rn/exp.json
Executable file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "glrn-ex",
|
||||
"description": "An empty new project",
|
||||
"slug": "glrn-ex",
|
||||
"sdkVersion": "11.0.3",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"primaryColor": "#cccccc",
|
||||
"iconUrl": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png",
|
||||
"notification": {
|
||||
"iconUrl": "https://s3.amazonaws.com/exp-us-standard/placeholder-push-icon-blue-circle.png",
|
||||
"color": "#000000"
|
||||
},
|
||||
"loading": {
|
||||
"iconUrl": "https://s3.amazonaws.com/exp-brand-assets/ExponentEmptyManifest_192.png",
|
||||
"hideExponentText": false
|
||||
},
|
||||
"packagerOpts": {
|
||||
"assetExts": ["ttf", "mp4"]
|
||||
}
|
||||
}
|
||||
32
cookbook-rn/main.js
Executable file
@ -0,0 +1,32 @@
|
||||
import Exponent from "exponent";
|
||||
import React from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import HelloGL from "./HelloGL";
|
||||
import HelloTexture from "./HelloTexture";
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<HelloGL />
|
||||
<HelloTexture />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: "#fff",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexDirection: "column",
|
||||
},
|
||||
});
|
||||
|
||||
Exponent.registerRootComponent(App);
|
||||
15
cookbook-rn/package.json
Executable file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "glrn-ex",
|
||||
"version": "0.0.0",
|
||||
"description": "Hello Exponent!",
|
||||
"author": null,
|
||||
"private": true,
|
||||
"main": "main.js",
|
||||
"dependencies": {
|
||||
"exponent": "^11.0.3-rc.1",
|
||||
"gl-react": "file:../packages/gl-react",
|
||||
"gl-react-native": "file:../packages/gl-react-native",
|
||||
"react": "~15.3.2",
|
||||
"react-native": "github:exponentjs/react-native#sdk-11.0.2"
|
||||
}
|
||||
}
|
||||
3
cookbook/.babelrc
Executable file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["react-app"]
|
||||
}
|
||||
7
cookbook/.flowconfig
Executable file
@ -0,0 +1,7 @@
|
||||
[ignore]
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[options]
|
||||
13386
cookbook/API.json
Executable file
21
cookbook/DocIntro.md
Executable file
@ -0,0 +1,21 @@
|
||||
|
||||
gl-react API documentation
|
||||
==========================
|
||||
|
||||
`gl-react` is a [React](https://facebook.github.io/react/) library to write and compose WebGL shaders.
|
||||
|
||||
This universal library must be coupled with one of the concrete implementations:
|
||||
|
||||
- [`gl-react-dom`](https://github.com/gre/gl-react/tree/master/packages/gl-react-dom/) for React DOM (web using WebGL).
|
||||
- [`gl-react-native`](https://github.com/gre/gl-react/tree/master/packages/gl-react-native/) for React Native (iOS/Android via OpenGL).
|
||||
- [`gl-react-headless`](https://github.com/gre/gl-react/tree/master/packages/gl-react-headless/) for Node.js (used for testing for now)
|
||||
|
||||
[](/hellogl)
|
||||
|
||||
```js
|
||||
<Surface width={300} height={200}>
|
||||
<Node shader={shaders.helloGL} />
|
||||
</Surface>
|
||||
```
|
||||
|
||||
There are two primitive components: [Surface](#surface) and [Node](#node).
|
||||
45
cookbook/package.json
Executable file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "gl-react-cookbook",
|
||||
"version": "3.0.0-alpha",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"babel-preset-react-app": "^1.0.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-scripts": "0.6.1"
|
||||
},
|
||||
"cookbook": {
|
||||
"githubprefix": "https://github.com/gre/gl-react/tree/master/cookbook/"
|
||||
},
|
||||
"dependencies": {
|
||||
"animated": "^0.1.3",
|
||||
"github-slugger": "^1.1.1",
|
||||
"gl-react": "file:../packages/gl-react",
|
||||
"gl-react-dom": "file:../packages/gl-react-dom",
|
||||
"gl-texture2d": "^2.0.12",
|
||||
"glsl-transitions": "^2016.10.24",
|
||||
"hoist-non-react-statics": "^1.2.0",
|
||||
"lodash": "^4.17.2",
|
||||
"ndarray": "^1.0.18",
|
||||
"ndarray-ops": "^1.2.2",
|
||||
"prism-theme-one-dark": "^1.0.0",
|
||||
"prismjs": "^1.5.1",
|
||||
"raf": "^3.3.0",
|
||||
"react": "^15.4.0",
|
||||
"react-addons-perf": "^15.4.0",
|
||||
"react-color": "^2.4.0",
|
||||
"react-dom": "^15.4.0",
|
||||
"react-json2d": "^0.1.0",
|
||||
"react-motion": "^0.4.5",
|
||||
"react-prism": "^4.0.0",
|
||||
"react-router": "^2.8.1",
|
||||
"react-sidebar": "^2.2.1",
|
||||
"remark": "^6.2.0",
|
||||
"remark-react": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"eject": "react-scripts eject",
|
||||
"generate-examples": "cd src/examples; ./gen.sh 1> index.js"
|
||||
}
|
||||
}
|
||||
BIN
cookbook/public/favicon.ico
Executable file
|
After Width: | Height: | Size: 13 KiB |
13
cookbook/public/index.html
Executable file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed:400,700" rel="stylesheet">
|
||||
<title>gl-react cookbook</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
236
cookbook/src/App.css
Executable file
@ -0,0 +1,236 @@
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
a:hover, a:focus {
|
||||
color: inherit;
|
||||
}
|
||||
.menu {
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
width: 280px;
|
||||
}
|
||||
.menu ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.menu li {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 4px;
|
||||
}
|
||||
.menu li a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.menu li .example-link.active {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.menu li .example-link strong {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.menu li .example-link.active strong {
|
||||
text-decoration: none;
|
||||
}
|
||||
.App {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.App > header {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flexDirection: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #000;
|
||||
position: relative;
|
||||
}
|
||||
.App > header .sidebar-opener {
|
||||
font-size: 32px;
|
||||
padding: 0px 15px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
.App > header .inspector-opener {
|
||||
font-size: 32px;
|
||||
padding: 0px 15px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
.App > header .logo {
|
||||
margin-left: 20px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.App > header .logo .t1 {
|
||||
color: #E24;
|
||||
}
|
||||
.App > header .logo .t2 {
|
||||
color: #000;
|
||||
}
|
||||
.App > header .logo .t3 {
|
||||
color: #E24;
|
||||
}
|
||||
.App > header .logo .v {
|
||||
font-weight: normal;
|
||||
font-size: 0.6em;
|
||||
margin-left: 10px;
|
||||
color: #333;
|
||||
}
|
||||
.App > header .logo img {
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.App > header nav {
|
||||
margin: 0 20px;
|
||||
}
|
||||
.App > header nav a {
|
||||
margin: 0 4px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.App > header h1 {
|
||||
font-size: 1.4em;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
.App > header select {
|
||||
margin: 0 20px;
|
||||
}
|
||||
a.left, a.right {
|
||||
font-size: 1.6em;
|
||||
padding: 0.6em 0.4em;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
a.left:hover, a.right:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
a.left {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
}
|
||||
a.right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
}
|
||||
.App .container {
|
||||
flex: 1;
|
||||
padding: 10px 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-around;
|
||||
flex-wrap: wrap;
|
||||
background: #111;
|
||||
overflow: auto;
|
||||
}
|
||||
.App .container .source {
|
||||
font-size: 12px;
|
||||
width: 600px;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.App .container .source .viewsource {
|
||||
color: #fff;
|
||||
text-align: right;
|
||||
opacity: 0.8;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.App .container .example {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
user-select: none;
|
||||
}
|
||||
.desc {
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
white-space: pre-line;
|
||||
display: inline-block;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.desc em {
|
||||
|
||||
}
|
||||
.desc pre {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
.desc blockquote {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
text-align: left;
|
||||
border-left: 2px solid #333;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.desc strong {
|
||||
color: #999;
|
||||
}
|
||||
.desc p {
|
||||
margin: 0;
|
||||
}
|
||||
.App .container .rendering {
|
||||
border: 1px solid #000;
|
||||
display: inline-block;
|
||||
background: #eee url(https://i.imgur.com/RE6MGrd.png) repeat;
|
||||
background-size: 20px 20px;
|
||||
line-height: 0px;
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
align-self: center;
|
||||
}
|
||||
.App .container .toolbox {
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.App .container .toolbox h3 {
|
||||
padding: 0;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.App .container .toolbox > * {
|
||||
padding: 5px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.App .container .example .buttons {
|
||||
text-align: center;
|
||||
padding: 1.8em;
|
||||
background: #fff;
|
||||
}
|
||||
.App .container .example .buttons button {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.inspector {
|
||||
width: 600px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
212
cookbook/src/App.js
Executable file
@ -0,0 +1,212 @@
|
||||
//@flow
|
||||
import React, { PureComponent, Component, PropTypes } from "react";
|
||||
import { findDOMNode } from "react-dom";
|
||||
import "./App.css";
|
||||
import {Link} from "react-router";
|
||||
import logopng from "./logo.png";
|
||||
import Code from "./Code";
|
||||
import Sidebar from "react-sidebar";
|
||||
import Inspector from "./Inspector";
|
||||
import pkg from "../package.json";
|
||||
|
||||
function triggerLink (linkRef) {
|
||||
const dom = linkRef && findDOMNode(linkRef);
|
||||
if (dom) dom.click();
|
||||
}
|
||||
|
||||
const lenseSidebar = ({ location: {query: {menu, inspector}} }) =>
|
||||
({
|
||||
menu,
|
||||
inspector,
|
||||
});
|
||||
|
||||
class MenuContext extends PureComponent {
|
||||
props: {
|
||||
examples: Array<*>,
|
||||
menu: bool,
|
||||
inspector: bool,
|
||||
currentExample: ?Object,
|
||||
};
|
||||
render() {
|
||||
const { examples, menu, inspector } = this.props;
|
||||
return <div>
|
||||
<h3>{examples.length} Examples</h3>
|
||||
<ul>
|
||||
{examples.map((ex, i) =>
|
||||
<li key={ex.path}>
|
||||
<Link
|
||||
to={{ pathname: ex.path, query: { menu, inspector } }}
|
||||
activeClassName="active"
|
||||
className="example-link">
|
||||
<strong>{ex.path}</strong>
|
||||
|
||||
<span>{ex.title}</span>
|
||||
</Link>
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Header extends Component {
|
||||
render() {
|
||||
const {
|
||||
currentExample,
|
||||
toToggleMenu,
|
||||
toToggleInspector,
|
||||
} = this.props;
|
||||
return <header>
|
||||
{ currentExample
|
||||
? <Link to={toToggleMenu} className="sidebar-opener">☰</Link>
|
||||
: null }
|
||||
<Link to="/" className="logo">
|
||||
<img alt="" src={logopng} />
|
||||
<span className="t1">gl</span>
|
||||
<span className="t2">-</span>
|
||||
<span className="t3">react</span>
|
||||
<span className="v">{pkg.version}</span>
|
||||
</Link>
|
||||
<nav>
|
||||
<Link to="/api">
|
||||
API
|
||||
</Link>
|
||||
<Link to={
|
||||
currentExample && currentExample.path==="hellogl"
|
||||
? "/hellogl?menu=true"
|
||||
: "/hellogl"
|
||||
}>
|
||||
Examples
|
||||
</Link>
|
||||
<a href="http://github.com/gre/gl-react">
|
||||
Github
|
||||
</a>
|
||||
</nav>
|
||||
<h1>
|
||||
{currentExample && currentExample.title}
|
||||
</h1>
|
||||
{ currentExample
|
||||
? <Link to={toToggleInspector} className="inspector-opener">☰</Link>
|
||||
: null }
|
||||
</header>;
|
||||
}
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("keydown", this.keydown, false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("keydown", this.keydown);
|
||||
}
|
||||
|
||||
keydown = (e: KeyboardEvent) => {
|
||||
switch(e.keyCode) {
|
||||
case 37: return e.metaKey && e.shiftKey && triggerLink(this.refs.left);
|
||||
case 39: return e.metaKey && e.shiftKey && triggerLink(this.refs.right);
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, location, routes } = this.props;
|
||||
const { menu, inspector } = lenseSidebar(this.props);
|
||||
const currentExample = routes[1].isExample ? routes[1] : null;
|
||||
const {LeftSidebar} = routes[1];
|
||||
const menuOpened = (LeftSidebar && !currentExample) || !!menu;
|
||||
const inspectorOpened = !!currentExample && !!inspector;
|
||||
const examples = this.props.route.childRoutes;
|
||||
const index = examples.indexOf(currentExample);
|
||||
let prev = examples[index - 1];
|
||||
if (prev && !prev.isExample) prev = null;
|
||||
let next = examples[index + 1];
|
||||
if (next && !next.isExample) next = null;
|
||||
return (
|
||||
<Sidebar
|
||||
docked={inspectorOpened}
|
||||
pullRight
|
||||
sidebarClassName="inspector"
|
||||
sidebar={
|
||||
inspectorOpened
|
||||
? <Inspector />
|
||||
: <span/>
|
||||
}>
|
||||
|
||||
<Sidebar
|
||||
docked={menuOpened}
|
||||
contentClassName="App"
|
||||
sidebarClassName="menu"
|
||||
sidebar={
|
||||
currentExample
|
||||
?
|
||||
<MenuContext
|
||||
menu={menu}
|
||||
inspector={inspector}
|
||||
currentExample={currentExample}
|
||||
examples={examples}
|
||||
/>
|
||||
:
|
||||
LeftSidebar ? <LeftSidebar /> : <span/>
|
||||
}>
|
||||
|
||||
<Header
|
||||
currentExample={currentExample}
|
||||
toToggleMenu={{
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
...location.query,
|
||||
menu: !menuOpened ? true : undefined,
|
||||
...(!menuOpened ? { inspector: undefined } : null),
|
||||
}
|
||||
}}
|
||||
toToggleInspector={{
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
...location.query,
|
||||
inspector: !inspectorOpened ? true : undefined,
|
||||
...(!inspectorOpened ? { menu: undefined } : null),
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{ prev
|
||||
?
|
||||
<Link ref="left" className="left" to={{
|
||||
pathname: prev.path,
|
||||
query: { menu, inspector },
|
||||
}}>❮</Link>
|
||||
: null }
|
||||
|
||||
{ next
|
||||
? <Link ref="right" className="right" to={{
|
||||
pathname: next.path,
|
||||
query: { menu, inspector },
|
||||
}}>❯</Link>
|
||||
: null }
|
||||
|
||||
<div className="container">
|
||||
|
||||
{children}
|
||||
|
||||
{ currentExample
|
||||
?
|
||||
<div className="source">
|
||||
<Code>{currentExample.source}</Code>
|
||||
<a className="viewsource" href={pkg.cookbook.githubprefix+"examples/"+currentExample.path}>
|
||||
view source
|
||||
</a>
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
|
||||
</Sidebar>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
13
cookbook/src/Code.css
Executable file
@ -0,0 +1,13 @@
|
||||
pre.cookbook-code a {
|
||||
color: inherit;
|
||||
}
|
||||
pre.cookbook-code .js-template-string-glsl {
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 4px 8px;
|
||||
display: block;
|
||||
max-height: 65vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
pre.cookbook-code {
|
||||
margin: 0;
|
||||
}
|
||||
41
cookbook/src/Code.js
Executable file
@ -0,0 +1,41 @@
|
||||
//@flow
|
||||
import React, { PureComponent } from "react";
|
||||
import "prismjs";
|
||||
const {Prism} = window;
|
||||
import "prismjs/plugins/autolinker/prism-autolinker";
|
||||
import "prismjs/components/prism-jsx";
|
||||
import "prismjs/components/prism-glsl";
|
||||
import "prism-theme-one-dark/prism-onedark.css";
|
||||
import { PrismCode } from "react-prism";
|
||||
import "./Code.css";
|
||||
|
||||
// add GLSL synthax for GLSL blocks.
|
||||
Prism.languages.insertBefore("jsx", "string", {
|
||||
"template-string": {
|
||||
pattern: /GLSL`(?:\\\\|\\?[^\\])*?`/,
|
||||
greedy: true,
|
||||
inside: {
|
||||
"string": /GLSL`[\n]?|`/,
|
||||
"js-template-string-glsl": {
|
||||
pattern: /[^`]*/,
|
||||
inside: {
|
||||
rest: Prism.languages.glsl,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
export default class Code extends PureComponent {
|
||||
props: {
|
||||
children?: any,
|
||||
};
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
return (
|
||||
<pre className="cookbook-code"><PrismCode className="language-jsx">
|
||||
{children}
|
||||
</PrismCode></pre>
|
||||
);
|
||||
}
|
||||
}
|
||||
63
cookbook/src/Dashboard.css
Executable file
@ -0,0 +1,63 @@
|
||||
.dashboard {
|
||||
color: #eee;
|
||||
margin: 10px auto;
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
.dashboard h2 {
|
||||
font-weight: normal;
|
||||
margin: 0.6em 0 1.2em 0;
|
||||
}
|
||||
|
||||
.dashboard nav {
|
||||
margin: 2em 0;
|
||||
font-size: 1.4em;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.dashboard nav a {
|
||||
display: block;
|
||||
padding: 0.2em 0;
|
||||
text-decoration: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.dashboard nav a:hover {
|
||||
text-decoration: underline;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dashboard .ex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.dashboard .showcode, .dashboard .showunderthehood {
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.dashboard .showcode:hover, .dashboard .showunderthehood:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.dashboard .showcode {
|
||||
text-align: center;
|
||||
transform: rotate(-90deg) translateX(-100%);
|
||||
transform-origin: top left;
|
||||
width: 200px;
|
||||
position: absolute;
|
||||
left: 200px;
|
||||
}
|
||||
.dashboard .showunderthehood {
|
||||
text-align: center;
|
||||
}
|
||||
.dashboard .ex > header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.dashboard .ex pre {
|
||||
flex: 1;
|
||||
}
|
||||
.dashboard .gl-react-inspector {
|
||||
background: #fff;
|
||||
height: 400px;
|
||||
}
|
||||
157
cookbook/src/Dashboard.js
Executable file
@ -0,0 +1,157 @@
|
||||
import React, {Component} from "react";
|
||||
import {Link} from "react-router";
|
||||
import Code from "./Code";
|
||||
import {Surface} from "gl-react-dom";
|
||||
import {Node, Shaders, GLSL, Backbuffer, LinearCopy} from "gl-react";
|
||||
import timeLoop from "./HOC/timeLoop";
|
||||
import "./Dashboard.css";
|
||||
import Inspector from "./Inspector";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
Persistence: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t, back;
|
||||
uniform float persistence;
|
||||
void main () {
|
||||
gl_FragColor = vec4(mix(
|
||||
texture2D(t, uv),
|
||||
texture2D(back, uv),
|
||||
persistence
|
||||
).rgb, 1.0);
|
||||
}`
|
||||
},
|
||||
hello: {
|
||||
// uniforms are variables from JS. We pipe blue uniform into blue output color
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform float red;
|
||||
void main() {
|
||||
gl_FragColor = vec4(red, uv.x, uv.y, 1.0);
|
||||
}` },
|
||||
Rotating: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform float angle, scale;
|
||||
uniform sampler2D children;
|
||||
void main() {
|
||||
mat2 rotation = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
|
||||
vec2 p = (uv - vec2(0.5)) * rotation / scale + vec2(0.5);
|
||||
gl_FragColor =
|
||||
p.x < 0.0 || p.x > 1.0 || p.y < 0.0 || p.y > 1.0
|
||||
? vec4(0.0)
|
||||
: texture2D(children, p);
|
||||
}` }
|
||||
});
|
||||
|
||||
const MotionBlur = ({ children: t, persistence }) =>
|
||||
<Node
|
||||
shader={shaders.Persistence}
|
||||
backbuffering
|
||||
uniforms={{ t, back: Backbuffer, persistence }}
|
||||
/>;
|
||||
|
||||
// We can make a <HelloBlue blue={0.5} /> that will render the concrete <Node/>
|
||||
class HelloGL extends Component {
|
||||
props: {
|
||||
red: number,
|
||||
};
|
||||
render() {
|
||||
const { red } = this.props;
|
||||
return <Node shader={shaders.hello} uniforms={{ red }} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Rotate extends Component {
|
||||
props: {
|
||||
scale: number,
|
||||
angle: number,
|
||||
children: any,
|
||||
};
|
||||
render() {
|
||||
const { angle, scale, children } = this.props;
|
||||
return <Node shader={shaders.Rotating} uniforms={{ scale, angle, children }} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Ex1 extends Component {
|
||||
props: { time: number };
|
||||
state = {
|
||||
showCode: false,
|
||||
showInspector: false,
|
||||
};
|
||||
onShowCode = () => {
|
||||
this.setState({ showCode: true });
|
||||
};
|
||||
onShowInspector = () => {
|
||||
this.setState({ showInspector: true });
|
||||
};
|
||||
render() {
|
||||
const { time } = this.props;
|
||||
const { showCode, showInspector } = this.state;
|
||||
const persistence = 0.75 - 0.20 * Math.cos(0.0005 * time);
|
||||
const red = 0.6 + 0.4 * Math.cos(0.004 * time);
|
||||
const scale = 0.70 + 0.40 * Math.cos(0.001 * time);
|
||||
const angle = 2 * Math.PI * (0.5 + 0.5 * Math.cos(0.001 * time));
|
||||
return (
|
||||
<div className="ex">
|
||||
<header>
|
||||
<Surface width={200} height={200}>
|
||||
<LinearCopy>
|
||||
<MotionBlur persistence={persistence}>
|
||||
<Rotate scale={scale} angle={angle}>
|
||||
<HelloGL red={red} />
|
||||
</Rotate>
|
||||
</MotionBlur>
|
||||
</LinearCopy>
|
||||
</Surface>
|
||||
{ !showCode
|
||||
? <div onClick={this.onShowCode} className="showcode">SHOW ME THE CODE!</div>
|
||||
:
|
||||
<Code>{
|
||||
` <Surface width={200} height={200}>
|
||||
<MotionBlur persistence={${persistence.toFixed(2)}}>
|
||||
<Rotate scale={${scale.toFixed(2)}} angle={${angle.toFixed(2)}}>
|
||||
<HelloGL red={${red.toFixed(1)}} />
|
||||
</Rotate>
|
||||
</MotionBlur>
|
||||
</Surface>`
|
||||
}</Code> }
|
||||
</header>
|
||||
{ showCode
|
||||
?
|
||||
!showInspector
|
||||
? <div onClick={this.onShowInspector} className="showunderthehood">SHOW ME UNDER THE HOOD!</div>
|
||||
: <Inspector />
|
||||
: null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Ex1Loop = timeLoop(Ex1);
|
||||
|
||||
export default class Dashboard extends Component {
|
||||
render() {
|
||||
return <div className="dashboard">
|
||||
<h2>
|
||||
gl-react is a <a href="http://facebook.github.io/react/">React</a> library to write and compose WebGL shaders.
|
||||
</h2>
|
||||
<Ex1Loop />
|
||||
<nav>
|
||||
<Link to="/hellogl">
|
||||
Checkout more examples
|
||||
</Link>
|
||||
<a href="http://github.com/gre/gl-react">
|
||||
Explore source code on Github
|
||||
</a>
|
||||
<a href="https://discordapp.com/channels/102860784329052160/106102146109325312">
|
||||
Chat with us, #gl-react on reactiflux
|
||||
</a>
|
||||
</nav>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
541
cookbook/src/Docs/index.js
Executable file
@ -0,0 +1,541 @@
|
||||
//@flow
|
||||
// NB This is our custom version of documentation html renderer.
|
||||
// everything is intentionally inlined here so we can do custom things.
|
||||
|
||||
import React, {
|
||||
Component,
|
||||
PureComponent,
|
||||
} from "react";
|
||||
import GithubSlugger from "github-slugger";
|
||||
import remark from "remark";
|
||||
import remarkReactRenderer from "remark-react";
|
||||
import API from "../../API.json";
|
||||
import "./style.css";
|
||||
import Code from "../Code";
|
||||
import DocIntroMD from "raw!../../DocIntro.md";
|
||||
|
||||
const paths = {
|
||||
"Component": "https://facebook.github.io/react/docs/react-component.html",
|
||||
"WebGLRenderingContext": "https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14",
|
||||
"WebGLContextAttributes": "https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.2",
|
||||
|
||||
};
|
||||
API.forEach(doc => {
|
||||
paths[doc.name] = "#"+slug(doc.name);
|
||||
});
|
||||
|
||||
function getHref (text: string) {
|
||||
return text in paths ? paths[text] : null;
|
||||
}
|
||||
|
||||
function t (text) {
|
||||
return <span>{text}</span>;
|
||||
}
|
||||
|
||||
function visit(tree, type, visitor, reverse) {
|
||||
if (typeof type === "function") {
|
||||
reverse = visitor;
|
||||
visitor = type;
|
||||
type = null;
|
||||
}
|
||||
function all(children, parent) {
|
||||
var step = reverse ? -1 : 1;
|
||||
var max = children.length;
|
||||
var min = -1;
|
||||
var index = (reverse ? max : min) + step;
|
||||
var child;
|
||||
while (index > min && index < max) {
|
||||
child = children[index];
|
||||
if (child && one(child, index, parent) === false) {
|
||||
return false;
|
||||
}
|
||||
index += step;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function one(node, index, parent) {
|
||||
var result;
|
||||
index = index || (parent ? 0 : null);
|
||||
if (!type || node.type === type) {
|
||||
result = visitor(node, index, parent || null);
|
||||
}
|
||||
if (node.children && result !== false) {
|
||||
return all(node.children, node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
one(tree);
|
||||
}
|
||||
|
||||
function rerouteLinks (ast) {
|
||||
visit(ast, "link", function (node) {
|
||||
if (node.jsdoc && !node.url.match(/^(http|https|\.)/) && getHref(node.url)) {
|
||||
node.url = getHref(node.url);
|
||||
}
|
||||
});
|
||||
return ast;
|
||||
}
|
||||
|
||||
function autolink(text) {
|
||||
var href = getHref(text);
|
||||
if (href) {
|
||||
return <a href={href}>{text}</a>;
|
||||
}
|
||||
return t(text);
|
||||
}
|
||||
|
||||
function commaList(getHref, items, start, end, sep) {
|
||||
var res = [];
|
||||
if (start) {
|
||||
res.push(t(start));
|
||||
}
|
||||
for (var i = 0, iz = items.length; i < iz; ++i) {
|
||||
res = res.concat(formatType(items[i]));
|
||||
if (i + 1 !== iz) {
|
||||
res.push(t(sep || ", "));
|
||||
}
|
||||
}
|
||||
if (end) {
|
||||
res.push(t(end));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function decorate(formatted, str, prefix) {
|
||||
if (prefix) {
|
||||
return [t(str)].concat(formatted);
|
||||
}
|
||||
return formatted.concat(t(str));
|
||||
}
|
||||
|
||||
function formatType(node) {
|
||||
var result = [];
|
||||
|
||||
if (!node) {
|
||||
return [t("any")];
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "NullableLiteral":
|
||||
return [t("?")];
|
||||
case "AllLiteral":
|
||||
return [t("any")];
|
||||
case "NullLiteral":
|
||||
return [t("null")];
|
||||
case "VoidLiteral":
|
||||
return [t("void")];
|
||||
case "UndefinedLiteral":
|
||||
return [autolink("undefined")];
|
||||
case "NameExpression":
|
||||
return [autolink(node.name)];
|
||||
case "ParameterType":
|
||||
return [t(node.name + ": ")].concat(formatType(node.expression));
|
||||
|
||||
case "TypeApplication":
|
||||
return formatType(node.expression)
|
||||
.concat(commaList(getHref, node.applications, "<", ">"));
|
||||
case "UnionType":
|
||||
return commaList(getHref, node.elements, "(", ")", " | ");
|
||||
case "ArrayType":
|
||||
return commaList(getHref, node.elements, "[", "]");
|
||||
case "RecordType":
|
||||
return commaList(getHref, node.fields, "{", "}");
|
||||
|
||||
case "FieldType":
|
||||
if (node.value) {
|
||||
return [t(node.key + ": ")].concat(formatType(node.value));
|
||||
}
|
||||
return [t(node.key)];
|
||||
|
||||
case "FunctionType":
|
||||
result = [t("function (")];
|
||||
if (node["this"]) {
|
||||
if (node["new"]) {
|
||||
result.push(t("new: "));
|
||||
} else {
|
||||
result.push(t("this: "));
|
||||
}
|
||||
result = result.concat(formatType(node["this"]));
|
||||
if (node.params.length !== 0) {
|
||||
result.push(t(", "));
|
||||
}
|
||||
}
|
||||
result = result.concat(commaList(getHref, node.params, "", ")"));
|
||||
if (node.result) {
|
||||
result = result.concat([t(": ")].concat(formatType(node.result)));
|
||||
}
|
||||
return result;
|
||||
|
||||
case "RestType":
|
||||
return [ "...", formatType(node.expression) ];
|
||||
case "OptionalType":
|
||||
return ["optional ", formatType(node.expression) ].concat(
|
||||
node.default ? t("(default " + node.default + ")") : []);
|
||||
case "NonNullableType":
|
||||
return decorate(formatType(node.expression), "!", node.prefix);
|
||||
case "NullableType":
|
||||
return decorate(formatType(node.expression), "?", node.prefix);
|
||||
case "StringLiteralType":
|
||||
return [<code>{JSON.stringify(node.value)}</code>];
|
||||
case "NumericLiteralType":
|
||||
case "BooleanLiteralType":
|
||||
return [<code>{String(node.value)}</code>];
|
||||
|
||||
default:
|
||||
throw new Error("Unknown type " + node.type);
|
||||
}
|
||||
}
|
||||
|
||||
function parameter (param, short) {
|
||||
if (short) {
|
||||
return (param.type && param.type.type === "OptionalType") ?
|
||||
"[" + param.name + "]" : param.name;
|
||||
}
|
||||
return [param.name+": ", formatType(param.type)];
|
||||
}
|
||||
|
||||
function parameters (section, short) {
|
||||
if (section.params) {
|
||||
return ["("].concat(section.params.map((param, i) =>
|
||||
[i===0?"":", ", parameter(param, short)])).concat([")"]);
|
||||
}
|
||||
return "()";
|
||||
}
|
||||
|
||||
function slug (str) {
|
||||
var slugger = new GithubSlugger();
|
||||
return slugger.slug(str);
|
||||
}
|
||||
|
||||
const MD = remark().use(remarkReactRenderer);
|
||||
|
||||
function md (ast, inline) {
|
||||
if (inline && ast && ast.children.length && ast.children[0].type === "paragraph") {
|
||||
ast = {
|
||||
type: "root",
|
||||
children: ast.children[0].children.concat(ast.children.slice(1))
|
||||
};
|
||||
}
|
||||
if (ast) ast = rerouteLinks(ast);
|
||||
if (!ast) return;
|
||||
const root = MD.stringify(ast);
|
||||
if (inline) {
|
||||
return <span className="md">{root.props.children}</span>;
|
||||
}
|
||||
return <div className="md">{root}</div>;
|
||||
}
|
||||
|
||||
function signature (section) {
|
||||
var returns = "";
|
||||
var prefix = "";
|
||||
if (section.kind === "class") {
|
||||
prefix = "new ";
|
||||
} else if (section.kind !== "function") {
|
||||
return section.name;
|
||||
}
|
||||
if (section.returns) {
|
||||
returns = [": ", formatType(section.returns[0].type)];
|
||||
}
|
||||
return [prefix + section.name, parameters(section), returns];
|
||||
}
|
||||
|
||||
function isReactComponent (section) {
|
||||
return section.augments && section.augments.some(a => a.name==="Component");
|
||||
}
|
||||
|
||||
function shortSignature (section) {
|
||||
var prefix = "";
|
||||
if (section.kind === "class") {
|
||||
if (isReactComponent(section)) {
|
||||
const props =
|
||||
(section.properties||[])
|
||||
.filter(p => p.type && p.type.type !== "OptionalType")
|
||||
.map(p => p.name);
|
||||
const attrs =
|
||||
props
|
||||
.filter(name => name!=="children")
|
||||
.map(name => " "+name+"={..}")
|
||||
.join("");
|
||||
const hasChildren = props.some(name => name==="children");
|
||||
return "<"+section.name+attrs+(hasChildren ? ">\n ...\n</"+section.name+">" : " />");
|
||||
}
|
||||
else {
|
||||
prefix = "new ";
|
||||
}
|
||||
} else if (section.kind !== "function") {
|
||||
return;
|
||||
}
|
||||
return [prefix + section.name, parameters(section, true)];
|
||||
}
|
||||
|
||||
function highlight (code) {
|
||||
return code ? <Code>{code}</Code> : null;
|
||||
}
|
||||
|
||||
class DocSectionList extends PureComponent {
|
||||
render() {
|
||||
const { members } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{members.map((member, i) =>
|
||||
<div key={i} className="member" id={member.namespace}>
|
||||
<div className="member-title">
|
||||
<code>
|
||||
.{signature(member)}
|
||||
</code>
|
||||
</div>
|
||||
<DocSection
|
||||
section={member}
|
||||
nested
|
||||
/>
|
||||
</div>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocSection extends PureComponent {
|
||||
render() {
|
||||
const { section, nested } = this.props;
|
||||
return (
|
||||
<section id={slug(section.namespace)} className={"section "+(nested?"nested":"")}>
|
||||
{(!nested || (section.context && section.context.github))
|
||||
?
|
||||
<div>
|
||||
{!nested
|
||||
?
|
||||
<h3>
|
||||
<a href={"#"+slug(section.namespace)}>{section.name}</a>
|
||||
|
||||
{section.augments
|
||||
?
|
||||
<span className="augments">
|
||||
extends
|
||||
{ section.augments
|
||||
? section.augments.map((tag, i) => <span key={i}>
|
||||
{i===0 ? "" : ", "}
|
||||
{autolink(tag.name)}
|
||||
</span>)
|
||||
: null }
|
||||
</span>
|
||||
: null }
|
||||
|
||||
{ section.context && section.context.github
|
||||
?
|
||||
<a className="github-link" href={section.context.github}>
|
||||
{section.context.path}
|
||||
</a>
|
||||
: null }
|
||||
</h3>
|
||||
: null }
|
||||
|
||||
{ !nested
|
||||
? highlight(shortSignature(section))
|
||||
: null }
|
||||
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{md(section.description)}
|
||||
|
||||
{ section.version ? <div>Version: {section.version}</div> : null }
|
||||
{ section.license ? <div>License: {section.license}</div> : null }
|
||||
{ section.author ? <div>Author: {section.author}</div> : null }
|
||||
{ section.copyright ? <div>Copyright: {section.copyright}</div> : null }
|
||||
{ section.since ? <div>Since: {section.since}</div> : null }
|
||||
|
||||
{section.params
|
||||
?
|
||||
<div>
|
||||
<h4>Parameters</h4>
|
||||
<div>
|
||||
{section.params.map((param, i) =>
|
||||
<div key={i}>
|
||||
<div>
|
||||
<strong><code>{param.name}</code></strong>
|
||||
<code className="type">
|
||||
({formatType(param.type)}
|
||||
{param.default
|
||||
? <span>(default <code>{param.default}</code>)</span>
|
||||
: null }
|
||||
)</code>
|
||||
{md(param.description, true)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{section.properties
|
||||
?
|
||||
<div>
|
||||
<h4>
|
||||
{isReactComponent(section) ? "Props" : "Properties"}
|
||||
</h4>
|
||||
<ul className="props">
|
||||
{section.properties.map((property, i) =>
|
||||
<li key={i} className="prop">
|
||||
<strong><code>{property.name}</code></strong>
|
||||
<code className="type">({formatType(property.type)})</code>
|
||||
{ property.default
|
||||
? <span>(default <code>{property.default}</code>)</span>
|
||||
: null }
|
||||
{ property.description
|
||||
? [": ", md(property.description, true)]
|
||||
: null }
|
||||
{ property.properties
|
||||
?
|
||||
<ul>
|
||||
{property.properties.map((property, i) =>
|
||||
<li key={i}>
|
||||
<code>{property.name}</code>
|
||||
|
||||
{formatType(property.type)}
|
||||
{property.default
|
||||
? <span>(default <code>{property.default}</code>)</span>
|
||||
: null}
|
||||
{md(property.description)}
|
||||
</li>)}
|
||||
</ul>
|
||||
: null }
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{section.returns
|
||||
? section.returns.map((ret, i) =>
|
||||
<div key={i}>
|
||||
<h4>Returns</h4>
|
||||
<code>{formatType(ret.type)}</code>
|
||||
{ ret.description
|
||||
? md(ret.description, true)
|
||||
: null }
|
||||
</div>)
|
||||
: null }
|
||||
|
||||
{section.throws
|
||||
?
|
||||
<div>
|
||||
<h4>Throws</h4>
|
||||
<ul>
|
||||
{section.throws.map((throws, i) =>
|
||||
<li key={i}>
|
||||
{formatType(throws.type)}
|
||||
{": "}
|
||||
{md(throws.description, true)}
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{ section.examples
|
||||
?
|
||||
<div>
|
||||
<h4>Example{section.examples.length>1?"s":""}</h4>
|
||||
{section.examples.map((example, i) =>
|
||||
<div key={i}>
|
||||
{ example.caption ? <p>{md(example.caption)}</p> : null }
|
||||
{highlight(example.description)}
|
||||
</div>)}
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{ section.members.static && section.members.static.length
|
||||
?
|
||||
<div>
|
||||
<h4>Static Members</h4>
|
||||
<DocSectionList
|
||||
members={section.members.static}
|
||||
noun="Static Member"
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{ section.members.instance && section.members.instance.length
|
||||
?
|
||||
<div>
|
||||
<h4>Instance Members</h4>
|
||||
<DocSectionList
|
||||
members={section.members.instance}
|
||||
noun="Instance Member"
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
|
||||
{ section.members.events && section.members.events.length
|
||||
?
|
||||
<div>
|
||||
<h4>Events</h4>
|
||||
<DocSectionList
|
||||
members={section.members.events}
|
||||
noun="Event"
|
||||
/>
|
||||
</div>
|
||||
: null }
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocBody extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="intro" id="summary">
|
||||
<DocIntro />
|
||||
</div>
|
||||
{API.map((s, i) =>
|
||||
s.kind !== "note"
|
||||
? <DocSection key={i} section={s} />
|
||||
: null)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DocIntro extends Component {
|
||||
render() {
|
||||
return MD.process(DocIntroMD).contents;
|
||||
}
|
||||
}
|
||||
|
||||
class Doc extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="documentation">
|
||||
<DocBody />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export class DocToc extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div className="documentation-toc">
|
||||
<a href="#summary">
|
||||
gl-react
|
||||
</a>
|
||||
<ul>
|
||||
{API.map((doc, i) =>
|
||||
<li key={i} className={[
|
||||
"kind-"+doc.kind,
|
||||
isReactComponent(doc) ? "react-component" : ""
|
||||
].join(" ")}>
|
||||
<a href={"#"+slug(doc.namespace)}>
|
||||
{doc.name}
|
||||
</a>
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Screen extends Component {
|
||||
render() {
|
||||
return <Doc />;
|
||||
}
|
||||
}
|
||||
157
cookbook/src/Docs/style.css
Executable file
@ -0,0 +1,157 @@
|
||||
.documentation {
|
||||
width: 100%;
|
||||
min-width: 600px;
|
||||
max-width: 900px;
|
||||
color: #555;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.documentation a[href]:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.documentation-toc ul {
|
||||
padding: 20px 5px;
|
||||
overflow: visible;
|
||||
list-style: none;
|
||||
}
|
||||
.documentation-toc li {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
|
||||
}
|
||||
.documentation-toc li a {
|
||||
padding: 4px 20px;
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
}
|
||||
.documentation-toc li.react-component a {
|
||||
color: #f56;
|
||||
}
|
||||
.documentation-toc li.react-component a:before {
|
||||
content: "<";
|
||||
}
|
||||
.documentation-toc li.react-component a:after {
|
||||
content: " />";
|
||||
}
|
||||
|
||||
.documentation-toc li.kind-typedef a {
|
||||
color: #69c;
|
||||
}
|
||||
.documentation-toc li.kind-typedef a:before {
|
||||
left: 0px;
|
||||
color: #69c;
|
||||
font-size: 10px;
|
||||
content: "type";
|
||||
margin-right: 4px;
|
||||
margin-left: -20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.documentation strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.documentation .intro {
|
||||
padding: 20px 40px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.documentation .section {
|
||||
padding: 20px 40px;
|
||||
background: #fff;
|
||||
margin: 40px 0;
|
||||
}
|
||||
|
||||
.documentation .section.nested {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.documentation .section h3 {
|
||||
color: #000;
|
||||
font-size: 1.8em;
|
||||
margin: 0.4em 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
}
|
||||
.documentation .section h3 .augments {
|
||||
font-weight: normal;
|
||||
font-size: 0.6em;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.documentation .section h3 .github-link {
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.documentation .section h4 {
|
||||
margin: 0;
|
||||
margin-left: -0.8em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.8em;
|
||||
font-size: 1.4em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.documentation .type {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.documentation .props {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.documentation .props li {
|
||||
padding: 0.4em 0;
|
||||
}
|
||||
|
||||
.documentation .member {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.documentation .member .member-title {
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
margin-left: -20px;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
.documentation .member > .section {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.documentation .md {
|
||||
|
||||
}
|
||||
|
||||
.documentation .md blockquote {
|
||||
opacity: 0.5;
|
||||
margin: 0.2em 0;
|
||||
padding: 0 0 0 20px;
|
||||
border-left: 3px solid #666;
|
||||
}
|
||||
|
||||
.documentation .md h1 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
.documentation .md h2 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
.documentation .md h3 {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.documentation .md h4,
|
||||
.documentation .md h5,
|
||||
.documentation .md h6 {
|
||||
font-size: 1em;
|
||||
}
|
||||
87
cookbook/src/ExamplePage.js
Executable file
@ -0,0 +1,87 @@
|
||||
import React, {PropTypes, Component} from "react";
|
||||
|
||||
const encodeQueryValue = value => JSON.stringify(value);
|
||||
const decodeQueryValue = value => JSON.parse(value);
|
||||
const encodeQuery = obj => {
|
||||
const values = {};
|
||||
for (let k in obj) {
|
||||
if (obj.hasOwnProperty(k)) {
|
||||
values[k] = encodeQueryValue(obj[k]);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
};
|
||||
const decodeQuery = query => {
|
||||
query = { ...query };
|
||||
for (let k in query) {
|
||||
if (query.hasOwnProperty(k)) {
|
||||
try {
|
||||
query[k] = decodeQueryValue(query[k]);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn(e);
|
||||
delete query[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
export default class ExamplePage extends Component {
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
setToolState = (obj: any) => {
|
||||
const { location: { pathname, query } } = this.props;
|
||||
this.context.router.replace({
|
||||
pathname,
|
||||
query: { ...query, ...encodeQuery(obj) }
|
||||
});
|
||||
};
|
||||
|
||||
onChangeField = prop => value => this.setToolState({ [prop]: value });
|
||||
|
||||
render() {
|
||||
const {
|
||||
route: { path, toolbox, ToolboxFooter, Example, desc, descAfter },
|
||||
location: { query }
|
||||
} = this.props;
|
||||
const props = {
|
||||
setToolState: this.setToolState,
|
||||
...Example.defaultProps,
|
||||
...decodeQuery(query),
|
||||
};
|
||||
return <div id={path} className="example">
|
||||
<div className="desc">{desc}</div>
|
||||
<div className="rendering">
|
||||
<Example {...props} />
|
||||
</div>
|
||||
{ toolbox
|
||||
? <div className="toolbox">
|
||||
{toolbox.map((field, i) =>
|
||||
<div key={i} className="field">
|
||||
{ field.title
|
||||
? <h3>{
|
||||
typeof field.title==="function"
|
||||
? field.title(props[field.prop])
|
||||
: field.title
|
||||
}</h3>
|
||||
: null }
|
||||
{ field.Editor
|
||||
? <field.Editor
|
||||
{...field}
|
||||
value={props[field.prop]}
|
||||
onChange={this.onChangeField(field.prop)}
|
||||
/>
|
||||
: null }
|
||||
</div>)}
|
||||
{ ToolboxFooter
|
||||
? <ToolboxFooter {...props} />
|
||||
: null}
|
||||
</div>
|
||||
: null }
|
||||
<div className="desc">{descAfter}</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
50
cookbook/src/HOC/timeLoop.js
Executable file
@ -0,0 +1,50 @@
|
||||
//@flow
|
||||
import React, { PureComponent } from "react";
|
||||
import raf from "raf";
|
||||
import hoistNonReactStatics from "hoist-non-react-statics";
|
||||
|
||||
// NB this is only an utility for the examples
|
||||
export default (
|
||||
C: ReactClass<*>,
|
||||
{ refreshRate = 60 }: { refreshRate?: number } = {}
|
||||
): ReactClass<*> => {
|
||||
class TL extends PureComponent {
|
||||
static displayName = `timeLoop(${C.displayName||C.name||""})`;
|
||||
state: { time: number };
|
||||
state = {
|
||||
time: 0,
|
||||
tick: 0,
|
||||
};
|
||||
_r: any;
|
||||
componentDidMount() {
|
||||
let startTime: number, lastTime: number;
|
||||
let interval = 1000 / refreshRate;
|
||||
lastTime = -interval;
|
||||
const loop = (t: number) => {
|
||||
this._r = raf(loop);
|
||||
if (!startTime) startTime = t;
|
||||
if (t - lastTime > interval) {
|
||||
lastTime = t;
|
||||
this.setState({
|
||||
time: t - startTime,
|
||||
tick: this.state.tick + 1,
|
||||
});
|
||||
}
|
||||
};
|
||||
this._r = raf(loop);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
raf.cancel(this._r);
|
||||
}
|
||||
render() {
|
||||
return <C
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
/>;
|
||||
}
|
||||
};
|
||||
|
||||
hoistNonReactStatics(TL, C);
|
||||
|
||||
return TL;
|
||||
}
|
||||
334
cookbook/src/Inspector/index.css
Executable file
@ -0,0 +1,334 @@
|
||||
.gl-react-inspector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
}
|
||||
.gl-react-inspector h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
.gl-react-inspector .no-surface {
|
||||
padding: 20px;
|
||||
}
|
||||
.gl-react-inspector .no-surface ul {
|
||||
font-size: 16px;
|
||||
}
|
||||
.gl-react-inspector .no-surface li span {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.gl-react-inspector > header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 4px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.gl-react-inspector > header label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.gl-react-inspector .btn {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
user-select: none;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gl-react-inspector .body {
|
||||
flex: 1;
|
||||
background: #f3f3f3;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gl-react-inspector .body .nodes {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AscDA0JcudXmQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABIElEQVR42u3cMQqAQAxE0YkK3v/Ago3gEULIe43lwvLZzqn0Ob/v4/wWd5IcYTUBCAABIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIABmqfx/qbLL5QUg1Xi2fYDe8+0DIAABuAIBIAAEgAAQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIACmsQ+wl30A7ANsPt8+AAIQgCsQAAJAAAgAASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJgGvsAe9kHwD7A5vPtAyAAAbgCASAABIAAEAACQAAIAAEgAASAABAAAkAACAABIAAEgAAQAAJAAAgAASAApql8f4niBWChF2e+DTF+hz42AAAAAElFTkSuQmCC');
|
||||
background-size: 64px 64px;
|
||||
}
|
||||
|
||||
.gl-react-inspector .body .hook-drawer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box {
|
||||
position: absolute;;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 180px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0px 0px 3px rgba(0,0,0,0);
|
||||
border: 1px solid #eee;
|
||||
background: #fff;
|
||||
transition: 0.2s border-color;
|
||||
}
|
||||
.gl-react-inspector .box.recent-draw {
|
||||
border-color: #999;
|
||||
transition: 0.1s border-color;
|
||||
}
|
||||
|
||||
/*
|
||||
.gl-react-inspector .box.node header .name {
|
||||
color: #C30;
|
||||
}
|
||||
.gl-react-inspector .box.bus header .name {
|
||||
color: #093;
|
||||
}
|
||||
*/
|
||||
|
||||
.gl-react-inspector .box header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 12px;
|
||||
padding: 0px 4px;
|
||||
margin-bottom: 4px;
|
||||
line-height: 24px;
|
||||
cursor: move;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box header .name {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
word-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.gl-react-inspector .box header .redraw {
|
||||
padding-left: 10px;
|
||||
padding: 0px 6px;
|
||||
min-width: 30px;
|
||||
line-height: 24px;
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
}
|
||||
.gl-react-inspector .box header .redraw:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.gl-react-inspector .box header .drawCount {
|
||||
font-size: 10px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box .content-html {
|
||||
max-height: 80px;
|
||||
overflow: auto;
|
||||
color: #aaa;
|
||||
padding: 2px 8px;
|
||||
font-size: 9px;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box.minimized {
|
||||
width: 80px;
|
||||
}
|
||||
.gl-react-inspector .box.minimized header {
|
||||
border-bottom: none;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
.gl-react-inspector .box.minimized footer {
|
||||
border-top: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
.gl-react-inspector .box.minimized .redraw,
|
||||
.gl-react-inspector .box.minimized .content-html {
|
||||
display: none;
|
||||
}
|
||||
.gl-react-inspector .box.minimized .uniform {
|
||||
line-height: 12px;
|
||||
}
|
||||
.gl-react-inspector .box.minimized .uniform .anchor-hook {
|
||||
height: 12px;
|
||||
}
|
||||
.gl-react-inspector .box.minimized .uniform:not(.type-sampler2D):not(.type-array-sampler2D) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box footer {
|
||||
font-size: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
padding: 2px 4px;
|
||||
margin-top: 4px;
|
||||
border-top: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box footer .minimize {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
left: -12px;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
.gl-react-inspector .box:hover footer .minimize {
|
||||
opacity: 0.2;
|
||||
}
|
||||
.gl-react-inspector .box footer:hover .minimize {
|
||||
opacity: 1;
|
||||
}
|
||||
.gl-react-inspector .box footer .dim {
|
||||
color: #888;
|
||||
font-size: 8px;
|
||||
}
|
||||
.gl-react-inspector .box footer .mode {
|
||||
padding-left: 4px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
||||
.gl-react-inspector .box .anchor-hook {
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box .hook {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.gl-react-inspector .uniforms {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.gl-react-inspector .uniforms .uniform {
|
||||
line-height: 18px;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.gl-react-inspector .uniforms .name {
|
||||
color: #000;
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.gl-react-inspector .uniforms .value-container {
|
||||
/*white-space: nowrap;*/
|
||||
word-break: break-all;
|
||||
padding-right: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gl-react-inspector .uniforms .val {
|
||||
color: #850;
|
||||
background: #ffc;
|
||||
padding: 1px 3px;
|
||||
border: 1px solid #ed9;
|
||||
}
|
||||
.gl-react-inspector .uniforms .value.type-sampler2D {
|
||||
text-align: right;
|
||||
}
|
||||
.gl-react-inspector .uniforms .value.type-sampler2D .val {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gl-react-inspector .uniforms .value-array .value-array,
|
||||
.gl-react-inspector .uniforms .value.type-sampler2D {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gl-react-inspector .uniforms .value-array > :first-child:before {
|
||||
content: "[";
|
||||
color: #aaa;
|
||||
}
|
||||
.gl-react-inspector .uniforms .value-array > :after {
|
||||
content: ",";
|
||||
color: #aaa;
|
||||
}
|
||||
.gl-react-inspector .uniforms .value-array > :last-child:after {
|
||||
content: "]";
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.gl-react-inspector .uniforms .meta-info {
|
||||
color: #ec6;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.gl-react-inspector .preview {
|
||||
}
|
||||
|
||||
.gl-react-inspector .preview {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.gl-react-inspector .connection circle {
|
||||
fill: #666;
|
||||
r: 1;
|
||||
}
|
||||
|
||||
.gl-react-inspector circle.hook,
|
||||
.gl-react-inspector circle.anchor {
|
||||
r: 2;
|
||||
fill: #666;
|
||||
}
|
||||
.gl-react-inspector circle.standalone-output {
|
||||
r: 2;
|
||||
fill: #000;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.gl-react-inspector path.connection-line {
|
||||
opacity: 0.1;
|
||||
stroke: #000;
|
||||
fill: none;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.gl-react-inspector .box.grabbed {
|
||||
border-color: #000;
|
||||
transition: none;
|
||||
}
|
||||
.gl-react-inspector .anchor-group.grabbed circle.hook,
|
||||
.gl-react-inspector .anchor-group.grabbed circle.anchor {
|
||||
fill: #000;
|
||||
}
|
||||
.gl-react-inspector .anchor-group.grabbed path.connection-line {
|
||||
stroke: #000;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.gl-react-inspector .preview canvas {
|
||||
background: #f3f3f3 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAARklEQVRYw+3XwQkAIAwEwYvYf8uxBcE8fMwWEAbuleruDDd6cOXzAAEBAQEBAQEBAQFf2tM/RJIyMSAgICAgICAgICDgZQeYxgVOKu5KXQAAAABJRU5ErkJggg==) repeat;
|
||||
background-size: 20px 20px;
|
||||
border: 1px solid #666;
|
||||
max-width: 100%;
|
||||
min-height: 64px;
|
||||
box-sizing: border-box;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
1476
cookbook/src/Inspector/index.js
Executable file
46
cookbook/src/examples/animated/index.js
Executable file
@ -0,0 +1,46 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Shaders, Node, GLSL } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import Animated from "animated";
|
||||
const shaders = Shaders.create({
|
||||
cursor: { frag: GLSL`
|
||||
precision lowp float; varying vec2 uv; uniform vec2 style;
|
||||
void main() {
|
||||
float dist = pow(1.0 - distance(style, uv), 8.0);
|
||||
gl_FragColor = vec4(smoothstep(2.0, 0.2, distance(style, uv)) * vec3(
|
||||
1.0 * dist + pow(1.0 - distance(style.y, uv.y), 16.0),
|
||||
0.5 * dist + pow(1.0 - distance(style.y, uv.y), 32.0),
|
||||
0.2 * dist + pow(1.0 - distance(style.x, uv.x), 32.0)), 1.0);
|
||||
}` }
|
||||
});
|
||||
class Cursor extends Component {
|
||||
render() {
|
||||
const { style: { x, y } } = this.props;
|
||||
return <Node shader={shaders.cursor} uniforms={{ style: [ x, y ] }} />;
|
||||
}
|
||||
}
|
||||
// using "style" is a hack. see https://github.com/animatedjs/animated/issues/45
|
||||
const AnimatedCursor = Animated.createAnimatedComponent(Cursor);
|
||||
|
||||
export default class Example extends Component {
|
||||
state = {
|
||||
style: new Animated.ValueXY({ x: 0.5, y: 0.5 })
|
||||
};
|
||||
onMouseMove = (e: any) => {
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
Animated.spring(this.state.style, {
|
||||
toValue: {
|
||||
x: (e.clientX - rect.left) / rect.width,
|
||||
y: (rect.bottom - e.clientY) / rect.height,
|
||||
}
|
||||
}).start();
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<Surface width={500} height={500} onMouseMove={this.onMouseMove}>
|
||||
<AnimatedCursor {...this.state} />
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
};
|
||||
5
cookbook/src/examples/animated/meta.js
Executable file
@ -0,0 +1,5 @@
|
||||
import markdown from "../../markdown";
|
||||
export const title = "Cursor spring effect with animated";
|
||||
export const desc = markdown`
|
||||
We can also use \`Animated\` animation library.
|
||||
`;
|
||||
55
cookbook/src/examples/behindasteroids/behind-asteroids/package.json
Executable file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "gre-js13k-2014",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"clean": "rm -rf build/; mkdir -p build target",
|
||||
"compileglsl": "./scripts/compileglslfiles.sh src/shaders build",
|
||||
"concat": "./scripts/concat.sh > build/build.js",
|
||||
"minify": "uglifyjs build/build.js -c --screw-ie8 -m -o build/build.min.js",
|
||||
"nominify": "cp build/build.js build/build.min.js",
|
||||
"gen": "cp src/target.html target/index.html && cp build/build.min.js target/b.js",
|
||||
"build": "export NODE_ENV=production; npm run clean && npm run compileglsl && npm run concat && npm run minify && npm run gen && npm run zip",
|
||||
"build-nominify": "npm run clean && npm run compileglsl && npm run concat && npm run nominify && npm run gen",
|
||||
"watch": "npm run build-nominify; wr 'npm run build-nominify' src/ scripts/",
|
||||
"liveserver": "mkdir -p target; cd target; live-server --no-browser",
|
||||
"zip": "cd target; zip -r ../target.zip .; cd ..; wc -c target.zip",
|
||||
"start": "budo index.js:bundle.js | garnish"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
"babelify",
|
||||
"glslify"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gre/js13k-2014.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/gre/js13k-2014/issues"
|
||||
},
|
||||
"homepage": "https://github.com/gre/js13k-2014",
|
||||
"devDependencies": {
|
||||
"babelify": "^6.4.0",
|
||||
"browserify": "^11.0.1",
|
||||
"budo": "^5.1.5",
|
||||
"eslint": "^1.3.0",
|
||||
"garnish": "^3.2.1",
|
||||
"glslify": "^2.3.1",
|
||||
"glslmin": "0.0.0",
|
||||
"live-server": "^0.8.1",
|
||||
"uglify-js": "^2.4.24",
|
||||
"uglifycss": "^0.0.17",
|
||||
"wr": "^1.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"gl-react": "^1.2.5",
|
||||
"gl-react-inspector": "0.0.2",
|
||||
"react": "^0.14.0",
|
||||
"react-dom": "^0.14.0"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Invalid arguments. Usage: $0 fromDir toDir" >&2;
|
||||
exit 1;
|
||||
fi;
|
||||
if [ "$1" == "$2" ]; then
|
||||
echo "fromDir and toDir must be different" >&2;
|
||||
exit 2;
|
||||
fi;
|
||||
if [ ! -d "$1" ]; then
|
||||
echo "fromDir must be a directory" >&2;
|
||||
exit 3;
|
||||
fi;
|
||||
if [ ! -d "$2" ]; then
|
||||
echo "toDir must be a directory" >&2;
|
||||
exit 4;
|
||||
fi;
|
||||
|
||||
for glsl in $1/*.frag $1/*.vert; do
|
||||
name=`basename $glsl`;
|
||||
cat $glsl | glslmin > $2/$name;
|
||||
done;
|
||||
47
cookbook/src/examples/behindasteroids/behind-asteroids/scripts/concat.sh
Executable file
@ -0,0 +1,47 @@
|
||||
|
||||
cat src/pre.js
|
||||
|
||||
if [ "$NODE_ENV" == "production" ]; then
|
||||
cat src/env_prod.js
|
||||
else
|
||||
cat src/env_dev.js
|
||||
fi;
|
||||
|
||||
# libs
|
||||
|
||||
cat src/lib/math.js
|
||||
cat src/lib/path.js
|
||||
cat src/lib/asteroids.font.js
|
||||
cat src/lib/webgl.js
|
||||
cat src/lib/jsfxr.js
|
||||
cat src/lib/audio.js
|
||||
|
||||
# shaders
|
||||
|
||||
cd build;
|
||||
for glsl in *.frag *.vert; do
|
||||
name=`echo $glsl | tr '.' '_' | tr '[:lower:]' '[:upper:]'`
|
||||
cat $glsl | ../scripts/wrapjs.sh $name
|
||||
echo
|
||||
done
|
||||
cd ..;
|
||||
|
||||
# game
|
||||
|
||||
cat src/setup.js
|
||||
cat src/state.js
|
||||
cat src/sounds.js
|
||||
cat src/input.js
|
||||
cat src/behaviors.js
|
||||
cat src/ai.js
|
||||
cat src/asteroids.js
|
||||
cat src/asteroidsIncoming.js
|
||||
cat src/bullets.js
|
||||
cat src/particles.js
|
||||
cat src/spaceship.js
|
||||
cat src/ufo.js
|
||||
cat src/ui.js
|
||||
cat src/effects.js
|
||||
cat src/game.js
|
||||
|
||||
cat src/post.js
|
||||
7
cookbook/src/examples/behindasteroids/behind-asteroids/scripts/wrapjs.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# usage: cat glslfile | ./wrapjs.sh varname
|
||||
|
||||
echo -n "var $1 ='"
|
||||
perl -p -e 's/\n/\\n/';
|
||||
echo -ne "';"
|
||||
328
cookbook/src/examples/behindasteroids/behind-asteroids/src/ai.js
Executable file
@ -0,0 +1,328 @@
|
||||
/* global
|
||||
DEBUG
|
||||
AIrotate: true
|
||||
AIboost: true
|
||||
AIshoot: true
|
||||
AIexcitement: true
|
||||
spaceship
|
||||
t dt
|
||||
asteroids
|
||||
bullets
|
||||
W H
|
||||
dist normAngle
|
||||
ufos
|
||||
playingSince
|
||||
ctx
|
||||
*/
|
||||
|
||||
/*
|
||||
if (DEBUG) {
|
||||
/* eslint-disable no-inner-declarations
|
||||
var AIdebug = [], AIdebugCircle = [];
|
||||
function drawAIDebug () {
|
||||
AIdebug.forEach(function (debug, i) {
|
||||
ctx.save();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.fillStyle = ctx.strokeStyle = "hsl("+Math.floor(360*i/AIdebug.length)+",80%,50%)";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(debug[0], debug[1]);
|
||||
ctx.lineTo(debug[2], debug[3]);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.arc(debug[0], debug[1], 2, 0, 2*Math.PI);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
});
|
||||
AIdebugCircle.forEach(function (debug, i) {
|
||||
ctx.save();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.fillStyle = ctx.strokeStyle = "hsl("+Math.floor(360*i/AIdebugCircle.length)+",80%,50%)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(debug[0], debug[1], Math.max(0, debug[2] * debug[3]), 0, 2*Math.PI);
|
||||
ctx.stroke();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(debug[0], debug[1], debug[3], 0, 2*Math.PI);
|
||||
ctx.stroke();
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.fillText(debug[2].toFixed(2), debug[0], debug[1]-debug[3]-2);
|
||||
ctx.restore();
|
||||
});
|
||||
}
|
||||
function clearDebug () {
|
||||
AIdebug = [];
|
||||
AIdebugCircle = [];
|
||||
}
|
||||
function addDebugCircle (p, value, radius) {
|
||||
AIdebugCircle.push([ p[0], p[1], value, radius ]);
|
||||
}
|
||||
function addDebug (p, v) {
|
||||
var d = 200;
|
||||
AIdebug.push([ p[0], p[1], p[0]+(v?d*v[0]:0), p[1]+(v?d*v[1]:0) ]);
|
||||
}
|
||||
function addPolarDebug (p, ang, vel) {
|
||||
var v = [
|
||||
vel * Math.cos(ang),
|
||||
vel * Math.sin(ang)
|
||||
];
|
||||
addDebug(p, v);
|
||||
}
|
||||
/* eslint-enable
|
||||
}
|
||||
*/
|
||||
|
||||
var closestAsteroidMemory, targetShootMemory, closestAsteroidMemoryT, targetShootMemoryT;
|
||||
|
||||
// AI states
|
||||
function aiLogic (smart) { // set the 3 AI inputs (rotate, shoot, boost)
|
||||
var i;
|
||||
|
||||
// DEBUG && clearDebug();
|
||||
|
||||
// first part is data extraction / analysis
|
||||
|
||||
//var ax = Math.cos(spaceship[4]);
|
||||
//var ay = Math.sin(spaceship[4]);
|
||||
var vel = Math.sqrt(spaceship[2]*spaceship[2]+spaceship[3]*spaceship[3]);
|
||||
var velAng = Math.atan2(spaceship[3], spaceship[2]);
|
||||
|
||||
//var spaceshipVel = [ ax * vel, ay * vel ];
|
||||
|
||||
|
||||
// utilities
|
||||
|
||||
function orient (ang) {
|
||||
var stableAng = normAngle(ang - spaceship[4]);
|
||||
AIrotate = stableAng < 0 ? -1 : 1;
|
||||
return stableAng;
|
||||
}
|
||||
|
||||
function move (ang, vel) {
|
||||
var stableAng = normAngle(ang - spaceship[4]);
|
||||
var abs = Math.abs(stableAng);
|
||||
if (abs > Math.PI/2) {
|
||||
if (vel) AIboost = abs>Math.PI/2-0.4 ? vel>0?-1:1 : 0;
|
||||
AIrotate = stableAng > 0 ? -1 : 1;
|
||||
}
|
||||
else {
|
||||
if (vel) AIboost = abs<0.4 ? vel<0?-1:1 : 0;
|
||||
AIrotate = stableAng < 0 ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// take actions to move and stabilize to a point
|
||||
function moveToPoint (p, minDist) {
|
||||
var dx = p[0]-spaceship[0];
|
||||
var dy = p[1]-spaceship[1];
|
||||
if (dx*dx+dy*dy<minDist*minDist) return;
|
||||
var tx = dx / 800;
|
||||
var ty = dy / 800;
|
||||
var x = tx - spaceship[2];
|
||||
var y = ty - spaceship[3];
|
||||
var ang = Math.atan2(y, x);
|
||||
var dist = length([x, y]);
|
||||
move(ang, dist);
|
||||
}
|
||||
|
||||
function dot (a,b) { // dot product
|
||||
return a[0]*b[0] + a[1]*b[1];
|
||||
}
|
||||
|
||||
// a line defined by AB, point is P
|
||||
function projectPointToLine (p, a, ab) {
|
||||
var ap = [ p[0]-a[0], p[1]-a[1] ];
|
||||
var k = dot(ap,ab)/dot(ab,ab);
|
||||
return [
|
||||
a[0] + k * ab[0],
|
||||
a[1] + k * ab[1]
|
||||
];
|
||||
}
|
||||
|
||||
function moveAwayFromPoint (p, v) {
|
||||
var spaceshipToP = [
|
||||
p[0] - spaceship[0],
|
||||
p[1] - spaceship[1]
|
||||
];
|
||||
var ang = Math.atan2(spaceshipToP[1], spaceshipToP[0]);
|
||||
var dist = length(spaceshipToP);
|
||||
// DEBUG && addPolarDebug(p, ang, 0.5);
|
||||
|
||||
if (v && vel > 0.003 * dist && Math.abs(normAngle(ang - velAng))<Math.PI/3) {
|
||||
// if there is some velocity, it is still good to "traverse" the point and not brake
|
||||
// but we need to target a bit more far from the obj vel
|
||||
var l = length(v);
|
||||
spaceshipToP[0] += 100 * v[0]/l;
|
||||
spaceshipToP[1] += 100 * v[1]/l;
|
||||
ang = Math.atan2(spaceshipToP[1], spaceshipToP[0]);
|
||||
move(ang, 1);
|
||||
}
|
||||
else {
|
||||
move(ang, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// danger have [ x, y, rot, vel ]
|
||||
function moveAwayFromAsteroid (ast) {
|
||||
var v = [
|
||||
ast[3] * Math.cos(ast[2]),// - spaceshipVel[0],
|
||||
ast[3] * Math.sin(ast[2])// - spaceshipVel[1]
|
||||
];
|
||||
var p = projectPointToLine(spaceship, ast, v);
|
||||
var acceptDist = 30 + 10 * ast[5];
|
||||
var d = dist(p, spaceship);
|
||||
if (d > acceptDist) return;
|
||||
//DEBUG && addDebug(p, v);
|
||||
moveAwayFromPoint(p, v);
|
||||
}
|
||||
|
||||
function predictShootIntersection (bulletVel, pos, target, targetVel) {
|
||||
// http://gamedev.stackexchange.com/a/25292
|
||||
var totarget = [
|
||||
target[0] - pos[0],
|
||||
target[1] - pos[1]
|
||||
];
|
||||
var a = dot(targetVel, targetVel) - bulletVel * bulletVel;
|
||||
var b = 2 * dot(targetVel, totarget);
|
||||
var c = dot(totarget, totarget);
|
||||
var p = -b / (2 * a);
|
||||
var q = Math.sqrt((b * b) - 4 * a * c) / (2 * a);
|
||||
var t1 = p - q;
|
||||
var t2 = p + q;
|
||||
var t = t1 > t2 && t2 > 0 ? t2 : t1;
|
||||
|
||||
return [t, [
|
||||
target[0] + targetVel[0] * t,
|
||||
target[1] + targetVel[1] * t
|
||||
]];
|
||||
}
|
||||
|
||||
var middle = [W/2,H/2];
|
||||
|
||||
var closestAsteroid, targetShoot, danger = 0;
|
||||
var closestAsteroidScore = 0.3, targetShootScore = 0.1;
|
||||
var incomingBullet, incomingBulletScore = 0;
|
||||
|
||||
for (i = 0; i < asteroids.length; ++i) {
|
||||
var ast = asteroids[i];
|
||||
// FIXME: take velocity of spaceship into account?
|
||||
var v = [
|
||||
ast[3] * Math.cos(ast[2]),
|
||||
ast[3] * Math.sin(ast[2])
|
||||
];
|
||||
var timeBeforeImpact = dot([ spaceship[0]-ast[0], spaceship[1]-ast[1] ],v)/dot(v,v);
|
||||
var impact = [
|
||||
ast[0] + timeBeforeImpact * v[0],
|
||||
ast[1] + timeBeforeImpact * v[1]
|
||||
];
|
||||
var distToImpact = dist(spaceship, impact);
|
||||
var distWithSize = distToImpact - 10 - 10 * ast[5];
|
||||
|
||||
var score =
|
||||
Math.exp(-distWithSize/40) +
|
||||
Math.exp(-distWithSize/120) +
|
||||
timeBeforeImpact > 0 ? Math.exp(-timeBeforeImpact/1000) : 0;
|
||||
|
||||
if (score > closestAsteroidScore) {
|
||||
closestAsteroidScore = score;
|
||||
closestAsteroid = ast;
|
||||
danger ++;
|
||||
}
|
||||
|
||||
score =
|
||||
Math.exp(-(ast[5]-1)) *
|
||||
Math.exp(-distWithSize/200);
|
||||
|
||||
if (score > targetShootScore) {
|
||||
var res = predictShootIntersection(0.3, spaceship, ast, v);
|
||||
var t = res[0];
|
||||
var p = res[1];
|
||||
if (0<p[0] && p[0]<W && 0<p[1] && p[1]<H && t<800) {
|
||||
targetShoot = p;
|
||||
targetShootScore = score;
|
||||
//DEBUG && addDebugCircle(p, score, 20);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (i = 0; i < bullets.length; ++i) {
|
||||
var b = bullets[i];
|
||||
v = b.slice(2);
|
||||
timeBeforeImpact = dot([ spaceship[0]-b[0], spaceship[1]-b[1] ],v)/dot(v,v);
|
||||
impact = [
|
||||
b[0] + timeBeforeImpact * v[0],
|
||||
b[1] + timeBeforeImpact * v[1]
|
||||
];
|
||||
distToImpact = dist(spaceship, impact);
|
||||
score = Math.exp(-timeBeforeImpact/1000) + 2*Math.exp(-distToImpact/50);
|
||||
if (100 < timeBeforeImpact &&
|
||||
timeBeforeImpact < 1000 &&
|
||||
distToImpact < 40 &&
|
||||
score > incomingBulletScore) {
|
||||
incomingBulletScore = score;
|
||||
incomingBullet = impact;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ufos.length; ++i) {
|
||||
var u = ufos[i];
|
||||
res = predictShootIntersection(0.3, spaceship, u, u.slice(2));
|
||||
t = res[0];
|
||||
p = res[1];
|
||||
targetShoot = p;
|
||||
}
|
||||
|
||||
AIexcitement =
|
||||
(1 - Math.exp(-asteroids.length/10)) + // total asteroids
|
||||
(1 - Math.exp(-danger/3)) // danger
|
||||
;
|
||||
|
||||
// Now we implement the spaceship reaction
|
||||
// From the least to the most important reactions
|
||||
|
||||
// Dump random changes
|
||||
|
||||
AIshoot = playingSince > 3000 && Math.random() < 0.001*dt*(1-smart);
|
||||
|
||||
AIrotate = (playingSince > 1000 && Math.random()<0.002*dt) ?
|
||||
(Math.random()<0.6 ? 0 : Math.random() < 0.5 ? -1 : 1) : AIrotate;
|
||||
|
||||
AIboost = (playingSince > 2000 && Math.random()<0.004*dt) ?
|
||||
(Math.random()<0.7 ? 0 : Math.random() < 0.5 ? -1 : 1) : AIboost;
|
||||
|
||||
// Stay in center area
|
||||
|
||||
if (0.1 + smart > Math.random()) moveToPoint(middle, 30);
|
||||
|
||||
// Shot the target
|
||||
|
||||
if (smart > Math.random()) {
|
||||
if (targetShoot) {
|
||||
AIshoot =
|
||||
Math.abs(orient(Math.atan2(
|
||||
targetShoot[1] - spaceship[1],
|
||||
targetShoot[0] - spaceship[0]))) < 0.1 &&
|
||||
Math.random() < 0.04 * dt;
|
||||
targetShootMemory = targetShoot;
|
||||
targetShootMemoryT = t;
|
||||
}
|
||||
else {
|
||||
AIshoot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid dangers
|
||||
if (smart > Math.random()) {
|
||||
if (closestAsteroid) {
|
||||
moveAwayFromAsteroid(closestAsteroid);
|
||||
closestAsteroidMemory = closestAsteroid;
|
||||
closestAsteroidMemoryT = closestAsteroid;
|
||||
}
|
||||
|
||||
if (incomingBullet) moveAwayFromPoint(incomingBullet);
|
||||
}
|
||||
|
||||
//DEBUG && targetShoot && addPolarDebug(targetShoot, 0, 0);
|
||||
//DEBUG && closestAsteroid && addPolarDebug(closestAsteroid, closestAsteroid[2], closestAsteroid[3]);
|
||||
}
|
||||
102
cookbook/src/examples/behindasteroids/behind-asteroids/src/asteroids.js
Executable file
@ -0,0 +1,102 @@
|
||||
/* global
|
||||
ctx path W H asteroids:true rotatingLetters incPosition incRotation MOBILE play
|
||||
Asend AsendFail
|
||||
*/
|
||||
|
||||
// Logic
|
||||
|
||||
function randomAsteroidShape (lvl) {
|
||||
var n = 4 + lvl * 2;
|
||||
var size = lvl * 10;
|
||||
var pts = [];
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var l = size*(0.4 + 0.6 * Math.random());
|
||||
var a = 2 * Math.PI * i / n;
|
||||
pts.push([
|
||||
l * Math.cos(a),
|
||||
l * Math.sin(a)
|
||||
]);
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
function randomAsteroids () {
|
||||
asteroids = [];
|
||||
for (var i=0; i<8; ++i) {
|
||||
var lvl = Math.floor(1.5 + 3 * Math.random());
|
||||
asteroids[i] = [
|
||||
W * Math.random(),
|
||||
H * Math.random(),
|
||||
2 * Math.PI * Math.random(),
|
||||
0.02 + 0.02 * Math.random(),
|
||||
randomAsteroidShape(lvl),
|
||||
lvl
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function explodeAsteroid (j) {
|
||||
var aster = asteroids[j];
|
||||
asteroids.splice(j, 1);
|
||||
var lvl = aster[5];
|
||||
if (lvl > 1) {
|
||||
var nb = Math.round(2+1.5*Math.random());
|
||||
for (var k=0; k<nb; k++) {
|
||||
var a = Math.random() + 2 * Math.PI * k / nb;
|
||||
asteroids.push([
|
||||
aster[0] + 10 * Math.cos(a),
|
||||
aster[1] + 10 * Math.sin(a),
|
||||
a,
|
||||
0.5 * aster[3],
|
||||
randomAsteroidShape(lvl-1),
|
||||
lvl - 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendAsteroid (o) {
|
||||
rotatingLetters.push(o[7]);
|
||||
if (Math.abs(Math.cos(o[2])) < o[9]) {
|
||||
var p = incPosition(o);
|
||||
var rot = incRotation(o);
|
||||
var x = Math.max(0, Math.min(p[0], W));
|
||||
var y = Math.max(0, Math.min(p[1], H));
|
||||
var vel = (MOBILE ? 0.006 : 0.008) * o[3];
|
||||
var lvl = o[6];
|
||||
var shape = o[5];
|
||||
asteroids.push([ x, y, rot, vel, shape, lvl ]);
|
||||
play(Asend);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
play(AsendFail);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
function randomInGameAsteroid () {
|
||||
var a = Math.random() < 0.5;
|
||||
var b = Math.random() < 0.5;
|
||||
var lvl = Math.floor(1 + 2 * Math.random() * Math.random());
|
||||
asteroids.push([
|
||||
a ? (b?-20:W+20) : W * Math.random(),
|
||||
!a ? (b?-20:H+20) : H * Math.random(),
|
||||
2 * Math.PI * Math.random(),
|
||||
0.02 + 0.02 * Math.random(),
|
||||
randomAsteroidShape(lvl),
|
||||
lvl
|
||||
]);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// RENDERING
|
||||
|
||||
function drawAsteroid (o) {
|
||||
ctx.globalAlpha = 0.2;
|
||||
ctx.strokeStyle = "#f00";
|
||||
path(o[4]);
|
||||
ctx.stroke();
|
||||
}
|
||||
259
cookbook/src/examples/behindasteroids/behind-asteroids/src/asteroidsIncoming.js
Executable file
@ -0,0 +1,259 @@
|
||||
/* global
|
||||
GAME_INC_PADDING W H t dt borderLength spaceship incomingObjects player
|
||||
playingSince randomAsteroidShape lifes dying ctx path MOBILE font helpVisible
|
||||
*/
|
||||
|
||||
function incPosition (o) {
|
||||
var i = o[0] % borderLength;
|
||||
var x, y;
|
||||
var w = W + GAME_INC_PADDING;
|
||||
var h = H + GAME_INC_PADDING;
|
||||
if (i<w) {
|
||||
x = i;
|
||||
y = 0;
|
||||
}
|
||||
else {
|
||||
i -= w;
|
||||
if (i < h) {
|
||||
x = w;
|
||||
y = i;
|
||||
}
|
||||
else {
|
||||
i -= h;
|
||||
if (i < w) {
|
||||
x = w - i;
|
||||
y = h;
|
||||
}
|
||||
else {
|
||||
i -= w;
|
||||
x = 0;
|
||||
y = h - i;
|
||||
}
|
||||
}
|
||||
}
|
||||
var p = [ -GAME_INC_PADDING/2 + x, -GAME_INC_PADDING/2 + y ];
|
||||
if (o[10]) {
|
||||
var dt = t - o[10];
|
||||
var a = Math.atan2(spaceship[1] - p[1], spaceship[0] - p[0]);
|
||||
var l = dt * 0.3;
|
||||
p[0] -= Math.cos(a) * l;
|
||||
p[1] -= Math.sin(a) * l;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
function incRotationCenter (o) {
|
||||
var p = incPosition(o);
|
||||
var toCenter = Math.atan2(spaceship[1] - p[1], spaceship[0] - p[0]);
|
||||
return toCenter;
|
||||
}
|
||||
|
||||
function incRotation (o) {
|
||||
return Math.cos(o[2]) * o[8] + incRotationCenter(o);
|
||||
//return o[2];
|
||||
}
|
||||
|
||||
var nextCreate = 0;
|
||||
function maybeCreateInc () {
|
||||
var sum = incomingObjects.reduce(function (sum, o) {
|
||||
return o[6];
|
||||
}, 0);
|
||||
// create inc is ruled with probabilities
|
||||
if (
|
||||
nextCreate < t &&
|
||||
Math.random() <
|
||||
0.01 * dt * // continous time probability
|
||||
Math.exp(-sum * // more there is object, more it is rare to create new ones
|
||||
(1 + 5 * Math.exp(-(player-1)/3) - 0.2 * Math.exp(-Math.abs(player-20)/20)) // first rounds have less items
|
||||
) *
|
||||
(1 - Math.exp(-playingSince / 5000))
|
||||
) {
|
||||
nextCreate = t + 1000 * (1 + Math.random());
|
||||
return createInc();
|
||||
}
|
||||
}
|
||||
|
||||
var rotatingLetters = [];
|
||||
for (var rotatingLettersI=0; rotatingLettersI<26; rotatingLettersI++)
|
||||
rotatingLetters.push(65+rotatingLettersI);
|
||||
rotatingLetters.sort(function () {
|
||||
return Math.random()-0.5;
|
||||
});
|
||||
|
||||
function createInc () {
|
||||
if (!rotatingLetters.length) return 0;
|
||||
var pos = Math.random() * borderLength;
|
||||
var key = rotatingLetters.shift();
|
||||
for (var i=0; i<incomingObjects.length; ++i) {
|
||||
var o = incomingObjects[i];
|
||||
var p = o[0] % borderLength;
|
||||
if (pos - 60 < p && p < pos + 60) return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
PARAMS to vary with game difficulty
|
||||
- higher rotation amplitude
|
||||
- lower rotation valid amp ratio
|
||||
- higher rotation speed
|
||||
*/
|
||||
|
||||
var diffMax = 1-Math.exp(-player/5);
|
||||
var diffMin = 1-Math.exp((1-player)/20);
|
||||
if (Math.random() > diffMax) diffMin *= Math.random();
|
||||
|
||||
|
||||
var pRotAmp = diffMin + Math.random() * (diffMax-diffMin);
|
||||
var pRotAmpRatio = diffMin + Math.random() * (diffMax-diffMin);
|
||||
var pRotSpeed = diffMin + Math.random() * (diffMax-diffMin);
|
||||
|
||||
var lvl = Math.floor(2 + 3 * Math.random() * Math.random() + 4 * Math.random() * Math.random() * Math.random());
|
||||
var ampRot = player<2 ? 0 : Math.PI * (0.8 * Math.random() + 0.05 * lvl) * pRotAmp;
|
||||
if (ampRot < 0.2) ampRot = 0;
|
||||
var ampRotRatio =
|
||||
player > 2 &&
|
||||
ampRot > Math.exp(-player/4) &&
|
||||
Math.random() > 0.5 + 0.4 * ((player-3)%8)/8 - 0.5 * (1 - Math.exp(-player/10)) ?
|
||||
0.9 - 0.5 * pRotAmpRatio - 0.2 * pRotAmp :
|
||||
1;
|
||||
|
||||
if (player == 2) {
|
||||
ampRot = 0.2 + Math.random();
|
||||
}
|
||||
|
||||
if (player == 3) {
|
||||
ampRot = 0.2 + Math.random();
|
||||
ampRotRatio = 0.5 + 0.4 * Math.random();
|
||||
}
|
||||
|
||||
incomingObjects.push([
|
||||
pos,
|
||||
// velocity
|
||||
0.1 + 0.002 * player,
|
||||
// initial angle
|
||||
2*Math.PI*Math.random(),
|
||||
// initial force
|
||||
10 + 40*Math.random(),
|
||||
// rot velocity
|
||||
0.002 + 0.001 * (Math.random() + 0.5 * lvl * Math.random() + Math.random() * player / 30) * pRotSpeed - 0.001 * pRotAmp,
|
||||
// shape
|
||||
randomAsteroidShape(lvl),
|
||||
// level
|
||||
lvl,
|
||||
// key
|
||||
key,
|
||||
// amplitude rotation
|
||||
ampRot,
|
||||
// amplitude rotation valid ratio
|
||||
ampRotRatio,
|
||||
// explode time
|
||||
0
|
||||
]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
function applyIncLogic (o) {
|
||||
if (!o[10]) {
|
||||
o[0] += o[1] * dt;
|
||||
o[2] += o[4] * dt;
|
||||
o[3] = o[3] < 10 ? 60 : o[3] - 0.02 * dt;
|
||||
}
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
|
||||
function drawInc (o) {
|
||||
var rotC = incRotationCenter(o);
|
||||
var phase = Math.cos(o[2]);
|
||||
var rot = phase * o[8] + rotC;
|
||||
var w = 10 * o[6];
|
||||
var valid = Math.abs(phase) < o[9];
|
||||
|
||||
if (playingSince>0 && lifes && !dying && !o[10]) {
|
||||
ctx.lineWidth = 1+o[3]/60;
|
||||
ctx.strokeStyle = valid ? "#7cf" : "#f66";
|
||||
|
||||
if (o[8] > 0.1) {
|
||||
ctx.save();
|
||||
ctx.rotate(rotC);
|
||||
ctx.strokeStyle = "#f66";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, w+10, -o[8], -o[8]*o[9]);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, w+10, o[8]*o[9], o[8]);
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = "#7cf";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, w+10, -o[8] * o[9], o[8] * o[9]);
|
||||
ctx.stroke();
|
||||
path([
|
||||
[w+8, 0],
|
||||
[w+12, 0]
|
||||
]);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.rotate(rot);
|
||||
ctx.save();
|
||||
var mx = 60 + w;
|
||||
var x = o[3] + w;
|
||||
ctx.globalAlpha = 0.2;
|
||||
path([
|
||||
[0,0],
|
||||
[mx,0]
|
||||
]);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
path([
|
||||
[0,0],
|
||||
[x,0]
|
||||
]);
|
||||
ctx.stroke();
|
||||
var r = 6;
|
||||
path([
|
||||
[ mx - r, r ],
|
||||
[ mx, 0],
|
||||
[ mx - r, -r ]
|
||||
], 1);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
else {
|
||||
ctx.strokeStyle = o[10] ? "#f66" : "#999";
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
path(o[5]);
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
var sum = [0, 0];
|
||||
o[5].forEach(function (p) {
|
||||
sum[0] += p[0];
|
||||
sum[1] += p[1];
|
||||
});
|
||||
|
||||
if (!MOBILE && playingSince>0) {
|
||||
if (helpVisible()) {
|
||||
ctx.strokeStyle = "#f7c";
|
||||
}
|
||||
ctx.translate(sum[0]/o[5].length+1, sum[1]/o[5].length-5);
|
||||
font(String.fromCharCode(o[7]), 1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawIncHelp () {
|
||||
if (!helpVisible()) return;
|
||||
ctx.strokeStyle = "#f7c";
|
||||
ctx.lineWidth = 4;
|
||||
incomingObjects.forEach(function (o) {
|
||||
var p = incPosition(o);
|
||||
ctx.beginPath();
|
||||
ctx.arc(p[0], p[1], 80 + 40 * Math.cos(0.005 * t), 0, 2*Math.PI);
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
41
cookbook/src/examples/behindasteroids/behind-asteroids/src/behaviors.js
Executable file
@ -0,0 +1,41 @@
|
||||
/* global dt W H */
|
||||
|
||||
function euclidPhysics (obj) {
|
||||
obj[0] += obj[2] * dt;
|
||||
obj[1] += obj[3] * dt;
|
||||
}
|
||||
|
||||
function polarPhysics (obj) {
|
||||
var x = Math.cos(obj[2]);
|
||||
var y = Math.sin(obj[2]);
|
||||
var s = dt * obj[3];
|
||||
obj[0] += s * x;
|
||||
obj[1] += s * y;
|
||||
}
|
||||
|
||||
function destroyOutOfBox (obj, i, arr) {
|
||||
if (obj[0] < -100 || obj[1] < -100 || obj[0] > W+100 || obj[1] > H+100) {
|
||||
arr.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function applyLife (obj, i, arr) {
|
||||
if ((obj[4] -= dt) < 0) {
|
||||
arr.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function loopOutOfBox (obj) {
|
||||
if (obj[0] < 0) {
|
||||
obj[0] += W;
|
||||
}
|
||||
else if (obj[0] > W) {
|
||||
obj[0] -= W;
|
||||
}
|
||||
if (obj[1] < 0) {
|
||||
obj[1] += H;
|
||||
}
|
||||
else if (obj[1] > H) {
|
||||
obj[1] -= H;
|
||||
}
|
||||
}
|
||||
27
cookbook/src/examples/behindasteroids/behind-asteroids/src/bullets.js
Executable file
@ -0,0 +1,27 @@
|
||||
/* global
|
||||
ctx bullets
|
||||
*/
|
||||
|
||||
function shoot (obj, vel, ang) {
|
||||
var ax = Math.cos(ang);
|
||||
var ay = Math.sin(ang);
|
||||
bullets.push([
|
||||
obj[0] + 14 * ax,
|
||||
obj[1] + 14 * ay,
|
||||
obj[2] + vel * ax,
|
||||
obj[3] + vel * ay,
|
||||
1000,
|
||||
0
|
||||
]);
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
|
||||
|
||||
function drawBullet () {
|
||||
ctx.globalAlpha = 1 - Math.random()*Math.random();
|
||||
ctx.fillStyle = "#00f";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 2+2.5*Math.random(), 0, 2*Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
139
cookbook/src/examples/behindasteroids/behind-asteroids/src/effects.js
vendored
Executable file
@ -0,0 +1,139 @@
|
||||
/* global
|
||||
g
|
||||
gl
|
||||
textureGame
|
||||
smoothstep
|
||||
glSetTexture
|
||||
glBindFBO
|
||||
glGetFBOTexture
|
||||
glUniformLocation
|
||||
glBindTexture
|
||||
laserFbo
|
||||
playerFbo
|
||||
glareFbo
|
||||
fbo1 fbo2
|
||||
persistenceFbo
|
||||
copyShader
|
||||
glBindShader
|
||||
laserShader
|
||||
playerShader
|
||||
blur1dShader
|
||||
gameShader
|
||||
glareShader
|
||||
persistenceShader
|
||||
t
|
||||
excitementSmoothed
|
||||
gameOver
|
||||
player
|
||||
playingSince
|
||||
lifes
|
||||
lastLoseShot
|
||||
shaking
|
||||
jumping
|
||||
dying
|
||||
*/
|
||||
|
||||
function drawPostProcessing () {
|
||||
glSetTexture(textureGame, g);
|
||||
|
||||
// Laser
|
||||
glBindFBO(laserFbo);
|
||||
glBindShader(laserShader);
|
||||
gl.uniform1i(glUniformLocation(laserShader, "t"), glBindTexture(textureGame, 0));
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
|
||||
// Player / env
|
||||
glBindFBO(playerFbo);
|
||||
glBindShader(playerShader);
|
||||
gl.uniform1f(glUniformLocation(playerShader, "pt"), playingSince / 1000);
|
||||
gl.uniform1f(glUniformLocation(playerShader, "pl"), player);
|
||||
gl.uniform1f(glUniformLocation(playerShader, "ex"), gameOver || excitementSmoothed);
|
||||
gl.uniform1f(glUniformLocation(playerShader, "J"), jumping);
|
||||
gl.uniform1f(glUniformLocation(playerShader, "P"), playingSince<0 || gameOver || dying ? 0 : 1);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo1);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(playerFbo), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 2, 2 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo2);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo1), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), -2, 2 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo1);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo2), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 6, 0 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(playerFbo);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo1), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 0, 2 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
|
||||
// Glare
|
||||
glBindFBO(glareFbo);
|
||||
glBindShader(glareShader);
|
||||
gl.uniform1i(glUniformLocation(glareShader, "t"), glBindTexture(glGetFBOTexture(laserFbo), 0));
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo1);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(glareFbo), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 2, -4 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(glareFbo);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo1), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 4, -8 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
|
||||
// Blur
|
||||
glBindFBO(fbo1);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(laserFbo), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 0.5, 0.5 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo2);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo1), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), -0.5, 0.5 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo1);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo2), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 1, 0 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(fbo2);
|
||||
glBindShader(blur1dShader);
|
||||
gl.uniform1i(glUniformLocation(blur1dShader, "t"), glBindTexture(glGetFBOTexture(fbo1), 0));
|
||||
gl.uniform2f(glUniformLocation(blur1dShader, "dir"), 0, 1 );
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
|
||||
// Persistence
|
||||
glBindFBO(fbo1);
|
||||
glBindShader(persistenceShader);
|
||||
gl.uniform1i(glUniformLocation(persistenceShader, "t"), glBindTexture(glGetFBOTexture(fbo2), 0));
|
||||
gl.uniform1i(glUniformLocation(persistenceShader, "r"), glBindTexture(glGetFBOTexture(persistenceFbo), 1));
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
glBindFBO(persistenceFbo);
|
||||
glBindShader(copyShader);
|
||||
gl.uniform1i(glUniformLocation(copyShader, "t"), glBindTexture(glGetFBOTexture(fbo1), 0));
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
|
||||
// Final draw
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||
glBindShader(gameShader);
|
||||
gl.uniform1i(glUniformLocation(gameShader, "G"), glBindTexture(glGetFBOTexture(laserFbo), 0));
|
||||
gl.uniform1i(glUniformLocation(gameShader, "R"), glBindTexture(glGetFBOTexture(persistenceFbo), 1));
|
||||
gl.uniform1i(glUniformLocation(gameShader, "B"), glBindTexture(glGetFBOTexture(fbo2), 2));
|
||||
gl.uniform1i(glUniformLocation(gameShader, "L"), glBindTexture(glGetFBOTexture(glareFbo), 3));
|
||||
gl.uniform1i(glUniformLocation(gameShader, "E"), glBindTexture(glGetFBOTexture(playerFbo), 4));
|
||||
gl.uniform1f(glUniformLocation(gameShader, "s"),
|
||||
!player ? smoothstep(-4000, -3000, playingSince) : 1);
|
||||
gl.uniform1f(glUniformLocation(gameShader, "F"),
|
||||
smoothstep(300, 0, t-lastLoseShot) +
|
||||
!gameOver && lifes>4 ? 0.5 * smoothstep(-1, 1, Math.cos(0.01*t)) : 0);
|
||||
gl.uniform2f(glUniformLocation(gameShader, "k"), shaking[0], shaking[1]);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
2
cookbook/src/examples/behindasteroids/behind-asteroids/src/env_dev.js
Executable file
@ -0,0 +1,2 @@
|
||||
var DEBUG = true; // eslint-disable-line no-unused-vars
|
||||
var MOBILE = "ontouchstart" in document; // eslint-disable-line no-unused-vars
|
||||
2
cookbook/src/examples/behindasteroids/behind-asteroids/src/env_prod.js
Executable file
@ -0,0 +1,2 @@
|
||||
var DEBUG = false; // eslint-disable-line no-unused-vars
|
||||
var MOBILE = "ontouchstart" in document; // eslint-disable-line no-unused-vars
|
||||
411
cookbook/src/examples/behindasteroids/behind-asteroids/src/game.js
Executable file
@ -0,0 +1,411 @@
|
||||
/* eslint-disable no-undef */
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
randomAsteroids();
|
||||
raf=requestAnimationFrame(render);
|
||||
|
||||
if (DEBUG) {
|
||||
/* eslint-disable */
|
||||
/*
|
||||
// DEBUG the game over screen
|
||||
setTimeout(function () {
|
||||
playingSince = -1;
|
||||
awaitingContinue = 0;
|
||||
player = 42;
|
||||
achievements = [123, 45, 6];
|
||||
gameOver = 1;
|
||||
}, 1000);
|
||||
*/
|
||||
// Debug the levels
|
||||
addEventListener("resize", function () {
|
||||
playingSince = -1;
|
||||
awaitingContinue = 0;
|
||||
player ++;
|
||||
incomingObjects = [];
|
||||
console.log("player=", player);
|
||||
/*
|
||||
ufos.push([
|
||||
0, 0, 0, 0, 0
|
||||
]);
|
||||
*/
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
setTimeout(function () {
|
||||
killSmoothed ++;
|
||||
}, 100);
|
||||
setTimeout(function () {
|
||||
killSmoothed ++;
|
||||
}, 2000);
|
||||
*/
|
||||
|
||||
// Debug the incomingObjects
|
||||
|
||||
/*
|
||||
setInterval(function () {
|
||||
createInc();
|
||||
if (incomingObjects[0]) sendAsteroid(incomingObjects[0]);
|
||||
incomingObjects.splice(0, 1);
|
||||
}, 800);
|
||||
*/
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
window._behindAsteroids_send = function () {
|
||||
createInc();
|
||||
if (incomingObjects[0]) sendAsteroid(incomingObjects[0]);
|
||||
incomingObjects.splice(0, 1);
|
||||
};
|
||||
|
||||
// Game Render Loop
|
||||
|
||||
var _lastT, _lastCheckSize = -9999;
|
||||
function render (_t) {
|
||||
raf=requestAnimationFrame(render);
|
||||
if (!_lastT) _lastT = _t;
|
||||
dt = Math.min(100, _t-_lastT);
|
||||
_lastT = _t;
|
||||
|
||||
if (t-_lastCheckSize>200) checkSize();
|
||||
|
||||
t += dt; // accumulate the game time (that is not the same as _t)
|
||||
|
||||
// UPDATE game
|
||||
update();
|
||||
|
||||
// RENDER game
|
||||
|
||||
// Game rendering
|
||||
|
||||
ctx = gameCtx;
|
||||
|
||||
ctx.save();
|
||||
|
||||
drawGame();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
RENDER_CB();
|
||||
}
|
||||
|
||||
// Game Update Loop
|
||||
|
||||
function update () {
|
||||
playingSince += dt;
|
||||
|
||||
if (t-ufoMusicTime>1200) {
|
||||
ufoMusicTime = t;
|
||||
if (ufos[0])
|
||||
play(Aufo);
|
||||
}
|
||||
|
||||
if(!gameOver && !awaitingContinue) {
|
||||
|
||||
if (playingSince > 0 && !achievements) {
|
||||
achievements = [0,0,0];
|
||||
}
|
||||
|
||||
var i;
|
||||
var nbSpaceshipBullets = 0;
|
||||
|
||||
if (!dying && playingSince>0 && t-musicPaused>5000 && player > 2 && !ufos.length) {
|
||||
|
||||
combosTarget = Math.floor(30 - 25 * Math.exp(-(player-3)/15));
|
||||
var musicFreq = 3*combos/combosTarget;
|
||||
if (combos > combosTarget) {
|
||||
musicPaused = t;
|
||||
neverUFOs = combos = 0;
|
||||
ufos.push([
|
||||
W * Math.random(),
|
||||
H * Math.random(),
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]);
|
||||
achievements[2] ++;
|
||||
}
|
||||
|
||||
musicPhase += musicFreq*2*Math.PI*dt/1000;
|
||||
if ((Math.sin(musicPhase) > 0) !== musicTick) {
|
||||
musicTick = !musicTick;
|
||||
play(musicTick ? Amusic1 : Amusic2);
|
||||
}
|
||||
}
|
||||
|
||||
// randomly send some asteroids
|
||||
/*
|
||||
if (Math.random() < 0.001 * dt)
|
||||
randomInGameAsteroid();
|
||||
*/
|
||||
|
||||
// player lifecycle
|
||||
|
||||
if (lifes == 0 && playingSince > 0) {
|
||||
// player enter
|
||||
resurrectionTime = t;
|
||||
lifes = 4;
|
||||
player++;
|
||||
score = 0;
|
||||
scoreForLife = 10000;
|
||||
jumpingAmp = 0;
|
||||
jumpingFreq = 0;
|
||||
asteroids = [];
|
||||
ufos = [];
|
||||
play(Acoin);
|
||||
if (player > 1) {
|
||||
//localStorage.ba_pl = player;
|
||||
//localStorage.ba_ach = achievements;
|
||||
}
|
||||
}
|
||||
|
||||
// inc lifecycle
|
||||
|
||||
if (playingSince > 1000 && !dying) {
|
||||
for (i = 0; i < incomingObjects.length; i++) {
|
||||
var o = incomingObjects[i];
|
||||
if (!o[10]) {
|
||||
var p = incPosition(o);
|
||||
var matchingTap = tap && circleCollides(tap, p, (MOBILE ? 60 : 20) + 10 * o[6]);
|
||||
if (keys[o[7]] || matchingTap) {
|
||||
// send an asteroid
|
||||
neverPlayed = tap = keys[o[7]] = 0;
|
||||
if (sendAsteroid(o)) {
|
||||
achievements[0] ++;
|
||||
if (player > 3) combos ++;
|
||||
incomingObjects.splice(i--, 1);
|
||||
}
|
||||
else {
|
||||
// failed to aim (red aiming)
|
||||
score += 5000;
|
||||
combos = 0;
|
||||
lastLoseShot = o[10] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (t-o[10] > 1000)
|
||||
incomingObjects.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
tap = 0;
|
||||
|
||||
while(maybeCreateInc());
|
||||
}
|
||||
|
||||
// spaceship lifecycle
|
||||
|
||||
if (dying && t-dying > 2000 + (lifes>1 ? 0 : 2000)) {
|
||||
dying = 0;
|
||||
spaceship = [ W/2, H/2, 0, 0, 0 ];
|
||||
if (--lifes) {
|
||||
resurrectionTime = t;
|
||||
}
|
||||
else {
|
||||
// Player lost. game over
|
||||
playingSince = -5000;
|
||||
randomAsteroids();
|
||||
ufos = [];
|
||||
setTimeout(function(){ play(Aleave); }, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// score lifecycle
|
||||
|
||||
if (score >= scoreForLife) {
|
||||
lastExtraLife = t;
|
||||
lifes ++;
|
||||
scoreForLife += 10000;
|
||||
play(Alife);
|
||||
if (lifes > 5) {
|
||||
gameOver = 1;
|
||||
incomingObjects = [];
|
||||
ufos = [];
|
||||
randomAsteroids();
|
||||
//localStorage.ba_pl=0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dying && playingSince>0 && t - lastScoreIncrement > 100) {
|
||||
score += 10;
|
||||
lastScoreIncrement = t;
|
||||
}
|
||||
best = Math.max(best, score);
|
||||
|
||||
// collision
|
||||
|
||||
bullets.forEach(function (bull, i) {
|
||||
if (!bull[5]) nbSpaceshipBullets ++;
|
||||
var j;
|
||||
|
||||
if (bull[4]<900) {
|
||||
// bullet-spaceship collision
|
||||
if (!dying && circleCollides(bull, spaceship, 20)) {
|
||||
explose(bull);
|
||||
bullets.splice(i, 1);
|
||||
spaceshipDie();
|
||||
return;
|
||||
}
|
||||
|
||||
// bullet-ufo collision
|
||||
for (j = 0; j < ufos.length; ++j) {
|
||||
var ufo = ufos[j];
|
||||
if (circleCollides(bull, ufo, 20)) {
|
||||
explose(bull);
|
||||
bullets.splice(i, 1);
|
||||
ufos.splice(j, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < asteroids.length; ++j) {
|
||||
var aster = asteroids[j];
|
||||
var lvl = aster[5];
|
||||
// bullet-asteroid collision
|
||||
if (circleCollides(bull, aster, 10 * lvl)) {
|
||||
explose(bull);
|
||||
bullets.splice(i, 1);
|
||||
explodeAsteroid(j);
|
||||
score += 50 * Math.floor(0.4 * (6 - lvl) * (6 - lvl));
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!dying && playingSince > 0) asteroids.forEach(function (aster, j) {
|
||||
// asteroid-spaceship collision
|
||||
if (circleCollides(aster, spaceship, 10 + 10 * aster[5])) {
|
||||
if (t - resurrectionTime < 200) {
|
||||
// if spaceship just resurect, will explode the asteroid
|
||||
explodeAsteroid(j);
|
||||
}
|
||||
else {
|
||||
// otherwise, player die
|
||||
explose(spaceship);
|
||||
spaceshipDie();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// run spaceship AI
|
||||
AIexcitement = 0;
|
||||
if (!dying && playingSince > 0) {
|
||||
var ax = Math.cos(spaceship[4]);
|
||||
var ay = Math.sin(spaceship[4]);
|
||||
|
||||
// ai logic (determine the 3 inputs)
|
||||
aiLogic(1-Math.exp(-(player-0.8)/14));
|
||||
|
||||
// apply ai inputs with game logic
|
||||
|
||||
var rotSpeed = 0.004 + 0.003 * (1-Math.exp(-player/40));
|
||||
var accSpeed = 0.0003 - 0.0002 * Math.exp(-(player-1)/5) + 0.00001 * player;
|
||||
var shotRate = 100 + 1000 * Math.exp(-(player-1)/8) + 300 * Math.exp(-player/20);
|
||||
|
||||
spaceship[2] += AIboost * dt * accSpeed * ax;
|
||||
spaceship[3] += AIboost * dt * accSpeed * ay;
|
||||
spaceship[4] = normAngle(spaceship[4] + AIrotate * dt * rotSpeed);
|
||||
if (nbSpaceshipBullets < 3) {
|
||||
if (AIshoot && t-lastBulletShoot > shotRate) {
|
||||
lastBulletShoot = t;
|
||||
play(Ashot);
|
||||
shoot(spaceship, 0.3, spaceship[4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
euclidPhysics(spaceship);
|
||||
asteroids.forEach(polarPhysics);
|
||||
ufos.forEach(euclidPhysics);
|
||||
bullets.forEach(euclidPhysics);
|
||||
particles.forEach(polarPhysics);
|
||||
|
||||
ufos.forEach(applyUFOlogic);
|
||||
incomingObjects.forEach(applyIncLogic);
|
||||
|
||||
particles.forEach(applyLife);
|
||||
loopOutOfBox(spaceship);
|
||||
asteroids.forEach(playingSince > 0 && !awaitingContinue && !gameOver ? destroyOutOfBox : loopOutOfBox);
|
||||
ufos.forEach(loopOutOfBox);
|
||||
bullets.forEach(applyLife);
|
||||
bullets.forEach(loopOutOfBox);
|
||||
|
||||
excitementSmoothed += 0.04 * (AIexcitement - excitementSmoothed);
|
||||
AIboostSmoothed += 0.04 * (AIboost - AIboostSmoothed);
|
||||
|
||||
// handling jumping / shaking
|
||||
killSmoothed -= dt * 0.0003 * killSmoothed;
|
||||
jumpingAmpSmoothed += 0.04 * (jumpingAmp - jumpingAmpSmoothed);
|
||||
jumpingFreqSmoothed += 0.04 * (jumpingFreq - jumpingFreqSmoothed);
|
||||
if (killSmoothed > 1.3) {
|
||||
if (jumpingAmp < 0.5) {
|
||||
jumpingFreq = 1 + Math.random();
|
||||
jumpingAmp ++;
|
||||
}
|
||||
}
|
||||
if (killSmoothed < 0.8) {
|
||||
jumpingAmp = 0;
|
||||
}
|
||||
var prevPhase = jumpingPhase;
|
||||
jumpingPhase += jumpingFreq *2*Math.PI*dt/1000;
|
||||
jumping = jumpingAmpSmoothed * Math.pow(Math.cos(jumpingPhase), 2.0);
|
||||
if (Math.cos(prevPhase) < 0 && 0 < Math.cos(jumpingPhase)) {
|
||||
jumpingFreq = 1 + 3 * Math.random() * Math.random();
|
||||
}
|
||||
if (jumpingAmp < 0.5) {
|
||||
jumpingAmpSmoothed += 0.04 * (jumpingAmp - jumpingAmpSmoothed);
|
||||
}
|
||||
|
||||
var shake = jumpingAmp * Math.pow(smoothstep(0.2, 0.0, jumping), 0.5);
|
||||
if (shake > 0.5 && t-lastJump>100) {
|
||||
play(Ajump);
|
||||
lastJump = t;
|
||||
}
|
||||
shaking = [
|
||||
30 * shake * (Math.random()-0.5) / FW,
|
||||
30 * shake * (Math.random()-0.5) / FH
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Game DRAWING
|
||||
|
||||
function drawGame () {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
ctx.restore();
|
||||
|
||||
renderCollection(asteroids, drawAsteroid);
|
||||
renderCollection(ufos, drawUFO);
|
||||
renderCollection(bullets, drawBullet);
|
||||
renderCollection(particles, drawParticle);
|
||||
|
||||
if (playingSince > 0 && !awaitingContinue && !gameOver) {
|
||||
ctx.save();
|
||||
translateTo(spaceship);
|
||||
drawSpaceship(spaceship);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
drawGameUI();
|
||||
|
||||
drawGlitch();
|
||||
}
|
||||
|
||||
|
||||
function translateTo (p) {
|
||||
ctx.translate(p[0], p[1]);
|
||||
}
|
||||
|
||||
function renderCollection (coll, draw) {
|
||||
for (var i=0; i<coll.length; ++i) {
|
||||
ctx.save();
|
||||
translateTo(coll[i]);
|
||||
draw(coll[i]);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
86
cookbook/src/examples/behindasteroids/behind-asteroids/src/input.js
Executable file
@ -0,0 +1,86 @@
|
||||
/* global
|
||||
keys
|
||||
tap:true
|
||||
MOBILE
|
||||
c
|
||||
d
|
||||
gameOver
|
||||
W
|
||||
gameScale
|
||||
achievements:true
|
||||
player:true
|
||||
playingSince:true
|
||||
awaitingContinue:true
|
||||
*/
|
||||
|
||||
/*
|
||||
for (var i=0; i<99; ++i) keys[i] = 0;
|
||||
|
||||
var fullScreenRequested = 0;
|
||||
function onTap (e) {
|
||||
if (MOBILE && !fullScreenRequested && d.webkitRequestFullScreen){
|
||||
d.webkitRequestFullScreen();
|
||||
fullScreenRequested = 1;
|
||||
}
|
||||
|
||||
var r = c.getBoundingClientRect(),
|
||||
x = (e.clientX - r.left) / gameScale,
|
||||
y = (e.clientY - r.top) / gameScale;
|
||||
if (gameOver) {
|
||||
if(280 < y && y < 400) {
|
||||
if (W/2 - 180 < x && x < W/2 - 20) {
|
||||
open("https://twitter.com/intent/tweet?via=greweb&url="+
|
||||
encodeURIComponent(location.href)+
|
||||
"&text="+
|
||||
encodeURIComponent(
|
||||
"Reached Level "+player+
|
||||
" ("+(player*25)+"¢) with "+
|
||||
achievements[0]+"⬠ "+
|
||||
achievements[1]+"ᐃ "+
|
||||
achievements[2]+"🝞"
|
||||
));
|
||||
}
|
||||
else if (W/2 + 20 < x && x < W/2 + 180) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (awaitingContinue) {
|
||||
if (playingSince>0 && 170<y && y<310) {
|
||||
// continue game action
|
||||
if (x<W/2) { // YES
|
||||
player = awaitingContinue-1;
|
||||
playingSince = awaitingContinue = 0;
|
||||
achievements = localStorage.ba_ach.split(",").map(function (v) {
|
||||
return parseInt(v, 10);
|
||||
});
|
||||
}
|
||||
else { // NO
|
||||
playingSince = awaitingContinue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tap = [x, y];
|
||||
}
|
||||
}
|
||||
|
||||
if (MOBILE) {
|
||||
addEventListener("touchstart", function (e) {
|
||||
e.preventDefault();
|
||||
onTap(e.changedTouches[0]);
|
||||
});
|
||||
}
|
||||
else {
|
||||
addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
onTap(e);
|
||||
});
|
||||
addEventListener("keydown", function (e) {
|
||||
keys[e.which] = 1;
|
||||
});
|
||||
addEventListener("keyup", function (e) {
|
||||
keys[e.which] = 0;
|
||||
});
|
||||
}
|
||||
*/
|
||||
353
cookbook/src/examples/behindasteroids/behind-asteroids/src/lib/asteroids.font.js
Executable file
@ -0,0 +1,353 @@
|
||||
/* global ctx, path */
|
||||
|
||||
// implementing by hand Asteroid's font. see http://www.dafont.com/hyperspace.font
|
||||
|
||||
var FONT0 = [ // 0
|
||||
[0, 0],
|
||||
[2, 0],
|
||||
[2, 2],
|
||||
[0, 2],
|
||||
[0, 0]
|
||||
];
|
||||
var FONT5 = [ // 5
|
||||
[2, 0],
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
[0, 2]
|
||||
];
|
||||
var FONT = [
|
||||
FONT0,
|
||||
[ // 1
|
||||
[1, 0],
|
||||
[1, 2]
|
||||
],
|
||||
[ // 2
|
||||
[0, 0],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[2, 2]
|
||||
],
|
||||
[ // 3
|
||||
[0, 0],
|
||||
[2, 0],
|
||||
[2, 2],
|
||||
[0, 2],
|
||||
,
|
||||
[0, 1],
|
||||
[2, 1]
|
||||
],
|
||||
[ // 4
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[2, 1],
|
||||
,
|
||||
[2, 0],
|
||||
[2, 2]
|
||||
],
|
||||
FONT5,
|
||||
[ // 6
|
||||
[0, 0],
|
||||
[0, 2],
|
||||
[2, 2],
|
||||
[2, 1],
|
||||
[0, 1]
|
||||
],
|
||||
[ // 7
|
||||
[0, 0],
|
||||
[2, 0],
|
||||
[2, 2]
|
||||
],
|
||||
[ // 8
|
||||
[0, 0],
|
||||
[2, 0],
|
||||
[2, 2],
|
||||
[0, 2],
|
||||
[0, 0],
|
||||
,
|
||||
[0, 1],
|
||||
[2, 1]
|
||||
],
|
||||
[ // 9
|
||||
[2, 2],
|
||||
[2, 0],
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[2, 1]
|
||||
]
|
||||
];
|
||||
[
|
||||
[// A
|
||||
[0,2],
|
||||
[0,2/3],
|
||||
[1,0],
|
||||
[2,2/3],
|
||||
[2,2],
|
||||
,
|
||||
[0,4/3],
|
||||
[2,4/3]
|
||||
],
|
||||
[ // B
|
||||
[0, 1],
|
||||
[0, 0],
|
||||
[4/3,0],
|
||||
[2,1/3],
|
||||
[2,2/3],
|
||||
[4/3,1],
|
||||
[0,1],
|
||||
[0,2],
|
||||
[4/3,2],
|
||||
[2,5/3],
|
||||
[2,4/3],
|
||||
[4/3,1]
|
||||
],
|
||||
[// C
|
||||
[2,0],
|
||||
[0,0],
|
||||
[0,2],
|
||||
[2,2]
|
||||
],
|
||||
[// D
|
||||
[0,0],
|
||||
[1,0],
|
||||
[2,2/3],
|
||||
[2,4/3],
|
||||
[1,2],
|
||||
[0,2],
|
||||
[0,0]
|
||||
],
|
||||
[// E
|
||||
[2,0],
|
||||
[0,0],
|
||||
[0,2],
|
||||
[2,2],
|
||||
,
|
||||
[0,1],
|
||||
[1.5,1]
|
||||
],
|
||||
[// F
|
||||
[2,0],
|
||||
[0,0],
|
||||
[0,2],
|
||||
,
|
||||
[0,1],
|
||||
[2,1]
|
||||
],
|
||||
[// G
|
||||
[2,2/3],
|
||||
[2,0],
|
||||
[0,0],
|
||||
[0,2],
|
||||
[2,2],
|
||||
[2,4/3],
|
||||
[1,4/3]
|
||||
],
|
||||
[// H
|
||||
[0,0],
|
||||
[0,2],
|
||||
,
|
||||
[2,0],
|
||||
[2,2],
|
||||
,
|
||||
[0,1],
|
||||
[2,1]
|
||||
],
|
||||
[// I
|
||||
[0,0],
|
||||
[2,0],
|
||||
,
|
||||
[1,0],
|
||||
[1,2],
|
||||
,
|
||||
[0,2],
|
||||
[2,2]
|
||||
],
|
||||
[// J
|
||||
[2,0],
|
||||
[2,2],
|
||||
[1,2],
|
||||
[0,4/3]
|
||||
],
|
||||
[// K
|
||||
[0,0],
|
||||
[0,2],
|
||||
,
|
||||
[2,0],
|
||||
[0,1],
|
||||
[2,2]
|
||||
],
|
||||
[// L
|
||||
[0,0],
|
||||
[0,2],
|
||||
[2,2]
|
||||
],
|
||||
[// M
|
||||
[0,2],
|
||||
[0,0],
|
||||
[1,2/3],
|
||||
[2,0],
|
||||
[2,2]
|
||||
],
|
||||
[// N
|
||||
[0,2],
|
||||
[0,0],
|
||||
[2,2],
|
||||
[2,0]
|
||||
],
|
||||
FONT0,// O
|
||||
[// P
|
||||
[0,2],
|
||||
[0,0],
|
||||
[2,0],
|
||||
[2,1],
|
||||
[0,1]
|
||||
],
|
||||
[// Q
|
||||
[0,0],
|
||||
[2,0],
|
||||
[2,4/3],
|
||||
[1,2],
|
||||
[0,2],
|
||||
[0,0],
|
||||
,
|
||||
[2,2],
|
||||
[1,4/3]
|
||||
],
|
||||
[// R
|
||||
[0,2],
|
||||
[0,0],
|
||||
[2,0],
|
||||
[2,1],
|
||||
[0,1],
|
||||
[2,2]
|
||||
],
|
||||
FONT5,// S
|
||||
[// T
|
||||
[0,0],
|
||||
[2,0],
|
||||
,
|
||||
[1,0],
|
||||
[1,2]
|
||||
],
|
||||
[// U
|
||||
[0,0],
|
||||
[0,2],
|
||||
[2,2],
|
||||
[2,0]
|
||||
],
|
||||
[// V
|
||||
[0,0],
|
||||
[1,2],
|
||||
[2,0]
|
||||
],
|
||||
[// W
|
||||
[0,0],
|
||||
[0,2],
|
||||
[1,4/3],
|
||||
[2,2],
|
||||
[2,0]
|
||||
],
|
||||
[// X
|
||||
[0,0],
|
||||
[2,2],
|
||||
,
|
||||
[2,0],
|
||||
[0,2]
|
||||
],
|
||||
[// Y
|
||||
[0,0],
|
||||
[1,2/3],
|
||||
[2,0],
|
||||
,
|
||||
[1,2/3],
|
||||
[1,2]
|
||||
],
|
||||
[// Z
|
||||
[0,0],
|
||||
[2,0],
|
||||
[0,2],
|
||||
[2,2]
|
||||
]
|
||||
].forEach(function (c, i) {
|
||||
FONT[String.fromCharCode(65+i)] = c;
|
||||
});
|
||||
|
||||
var dot = FONT["."] = [
|
||||
[1, 1.8],
|
||||
[1, 2]
|
||||
];
|
||||
|
||||
FONT[":"] = [
|
||||
[1, 0],
|
||||
[1, 0.2],
|
||||
,
|
||||
[1, 1.8],
|
||||
[1, 2]
|
||||
];
|
||||
|
||||
FONT["'"] = [
|
||||
[1, 0],
|
||||
[1, 2/3]
|
||||
];
|
||||
|
||||
FONT["ᐃ"] = [
|
||||
[ 1, 0 ],
|
||||
[ 1.8, 2 ],
|
||||
[ 1, 1.6 ],
|
||||
[ 0.2, 2 ],
|
||||
[ 1, 0 ]
|
||||
/*
|
||||
[-4, -4],
|
||||
[ 10, 0],
|
||||
[ -4, 4],
|
||||
[ -3, 0]
|
||||
*/
|
||||
];
|
||||
|
||||
FONT["!"] = [
|
||||
[1, 0],
|
||||
[1, 1.5],
|
||||
,
|
||||
].concat(dot);
|
||||
FONT["?"] = [
|
||||
[0, 0],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[1, 1],
|
||||
[1, 1.5],
|
||||
,
|
||||
].concat(dot);
|
||||
FONT["x"] = [
|
||||
[0,1],
|
||||
[2,2],
|
||||
,
|
||||
[2,1],
|
||||
[0,2]
|
||||
];
|
||||
FONT["¢"] = [
|
||||
[1,0],
|
||||
[1,2],
|
||||
,
|
||||
[1.5,0.5],
|
||||
[0.5,0.5],
|
||||
[0.5,1.5],
|
||||
[1.5,1.5]
|
||||
];
|
||||
|
||||
// oO ASTEROIDS font with fontSize and align (-1:right, 0:center, 1:left)
|
||||
// will side effect some ctx.translate() (that you could benefit to make text follow)
|
||||
function font (txt, fontSize, align) { // eslint-disable-line
|
||||
var l = fontSize*11*txt.length;
|
||||
ctx.translate(align ? (align>0 ? 0 : -l) : -l/2, 0);
|
||||
for (var i=0; i<txt.length; i++) {
|
||||
path(FONT[txt[i]] && FONT[txt[i]].map(function (o) {
|
||||
return o && [4*fontSize*o[0], 5*fontSize*o[1]];
|
||||
}), 1);
|
||||
ctx.lineJoin = "round";
|
||||
ctx.stroke();
|
||||
ctx.translate(fontSize*11, 0);
|
||||
}
|
||||
}
|
||||
35
cookbook/src/examples/behindasteroids/behind-asteroids/src/lib/audio.js
Executable file
@ -0,0 +1,35 @@
|
||||
/* global jsfxr */
|
||||
|
||||
var audioCtx, audioDest, audio, play; // eslint-disable-line
|
||||
|
||||
var AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
|
||||
if (AudioContext) {
|
||||
audioCtx = new AudioContext();
|
||||
audioDest = audioCtx.createDynamicsCompressor();
|
||||
var gain = audioCtx.createGain();
|
||||
gain.gain.value = 0.1;
|
||||
audioDest.connect(gain);
|
||||
gain.connect(audioCtx.destination);
|
||||
|
||||
audio = function (conf) { // eslint-disable-line no-unused-vars
|
||||
var o = [];
|
||||
jsfxr(conf, audioCtx, function (buf) {
|
||||
o.push(buf);
|
||||
});
|
||||
return o;
|
||||
};
|
||||
play = function (o) { // eslint-disable-line no-unused-vars
|
||||
if (!o[0]) return;
|
||||
var source = audioCtx.createBufferSource();
|
||||
source.buffer = o[0];
|
||||
source.start(0);
|
||||
source.connect(audioDest);
|
||||
setTimeout(function () {
|
||||
source.disconnect(audioDest);
|
||||
}, o[0].duration * 1000 + 300);
|
||||
};
|
||||
}
|
||||
else {
|
||||
audio = play = function(){};
|
||||
}
|
||||
490
cookbook/src/examples/behindasteroids/behind-asteroids/src/lib/jsfxr.js
Executable file
@ -0,0 +1,490 @@
|
||||
/**
|
||||
* SfxrParams
|
||||
*
|
||||
* Copyright 2010 Thomas Vian
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @author Thomas Vian
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
/** @constructor */
|
||||
function SfxrParams() {
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Settings String Methods
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parses a settings array into the parameters
|
||||
* @param array Array of the settings values, where elements 0 - 23 are
|
||||
* a: waveType
|
||||
* b: attackTime
|
||||
* c: sustainTime
|
||||
* d: sustainPunch
|
||||
* e: decayTime
|
||||
* f: startFrequency
|
||||
* g: minFrequency
|
||||
* h: slide
|
||||
* i: deltaSlide
|
||||
* j: vibratoDepth
|
||||
* k: vibratoSpeed
|
||||
* l: changeAmount
|
||||
* m: changeSpeed
|
||||
* n: squareDuty
|
||||
* o: dutySweep
|
||||
* p: repeatSpeed
|
||||
* q: phaserOffset
|
||||
* r: phaserSweep
|
||||
* s: lpFilterCutoff
|
||||
* t: lpFilterCutoffSweep
|
||||
* u: lpFilterResonance
|
||||
* v: hpFilterCutoff
|
||||
* w: hpFilterCutoffSweep
|
||||
* x: masterVolume
|
||||
* @return If the string successfully parsed
|
||||
*/
|
||||
this.ss = function(values)
|
||||
{
|
||||
for ( var i = 0; i < 24; i++ )
|
||||
{
|
||||
this[String.fromCharCode( 97 + i )] = values[i] || 0;
|
||||
}
|
||||
|
||||
// I moved this here from the r(true) function
|
||||
if (this['c'] < .01) {
|
||||
this['c'] = .01;
|
||||
}
|
||||
|
||||
var totalTime = this['b'] + this['c'] + this['e'];
|
||||
if (totalTime < .18) {
|
||||
var multiplier = .18 / totalTime;
|
||||
this['b'] *= multiplier;
|
||||
this['c'] *= multiplier;
|
||||
this['e'] *= multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SfxrSynth
|
||||
*
|
||||
* Copyright 2010 Thomas Vian
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @author Thomas Vian
|
||||
*/
|
||||
/** @constructor */
|
||||
function SfxrSynth() {
|
||||
// All variables are kept alive through function closures
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Sound Parameters
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
this._p = new SfxrParams(); // Params instance
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Synth Variables
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
var _envelopeLength0, // Length of the attack stage
|
||||
_envelopeLength1, // Length of the sustain stage
|
||||
_envelopeLength2, // Length of the decay stage
|
||||
|
||||
_period, // Period of the wave
|
||||
_maxPeriod, // Maximum period before sound stops (from minFrequency)
|
||||
|
||||
_slide, // Note slide
|
||||
_deltaSlide, // Change in slide
|
||||
|
||||
_changeAmount, // Amount to change the note by
|
||||
_changeTime, // Counter for the note change
|
||||
_changeLimit, // Once the time reaches this limit, the note changes
|
||||
|
||||
_squareDuty, // Offset of center switching point in the square wave
|
||||
_dutySweep; // Amount to change the duty by
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Synth Methods
|
||||
//
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* rs the runing variables from the params
|
||||
* Used once at the start (total r) and for the repeat effect (partial r)
|
||||
*/
|
||||
this.r = function() {
|
||||
// Shorter reference
|
||||
var p = this._p;
|
||||
|
||||
_period = 100 / (p['f'] * p['f'] + .001);
|
||||
_maxPeriod = 100 / (p['g'] * p['g'] + .001);
|
||||
|
||||
_slide = 1 - p['h'] * p['h'] * p['h'] * .01;
|
||||
_deltaSlide = -p['i'] * p['i'] * p['i'] * .000001;
|
||||
|
||||
if (!p['a']) {
|
||||
_squareDuty = .5 - p['n'] / 2;
|
||||
_dutySweep = -p['o'] * .00005;
|
||||
}
|
||||
|
||||
_changeAmount = 1 + p['l'] * p['l'] * (p['l'] > 0 ? -.9 : 10);
|
||||
_changeTime = 0;
|
||||
_changeLimit = p['m'] == 1 ? 0 : (1 - p['m']) * (1 - p['m']) * 20000 + 32;
|
||||
}
|
||||
|
||||
// I split the r() function into two functions for better readability
|
||||
this.tr = function() {
|
||||
this.r();
|
||||
|
||||
// Shorter reference
|
||||
var p = this._p;
|
||||
|
||||
// Calculating the length is all that remained here, everything else moved somewhere
|
||||
_envelopeLength0 = p['b'] * p['b'] * 100000;
|
||||
_envelopeLength1 = p['c'] * p['c'] * 100000;
|
||||
_envelopeLength2 = p['e'] * p['e'] * 100000 + 12;
|
||||
// Full length of the volume envelop (and therefore sound)
|
||||
// Make sure the length can be divided by 3 so we will not need the padding "==" after base64 encode
|
||||
return ((_envelopeLength0 + _envelopeLength1 + _envelopeLength2) / 3 | 0) * 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the wave to the supplied buffer ByteArray
|
||||
* @param buffer A ByteArray to write the wave to
|
||||
* @return If the wave is finished
|
||||
*/
|
||||
this.sw = function(buffer, length) {
|
||||
// Shorter reference
|
||||
var p = this._p;
|
||||
|
||||
// If the filters are active
|
||||
var _filters = p['s'] != 1 || p['v'],
|
||||
// Cutoff multiplier which adjusts the amount the wave position can move
|
||||
_hpFilterCutoff = p['v'] * p['v'] * .1,
|
||||
// Speed of the high-pass cutoff multiplier
|
||||
_hpFilterDeltaCutoff = 1 + p['w'] * .0003,
|
||||
// Cutoff multiplier which adjusts the amount the wave position can move
|
||||
_lpFilterCutoff = p['s'] * p['s'] * p['s'] * .1,
|
||||
// Speed of the low-pass cutoff multiplier
|
||||
_lpFilterDeltaCutoff = 1 + p['t'] * .0001,
|
||||
// If the low pass filter is active
|
||||
_lpFilterOn = p['s'] != 1,
|
||||
// masterVolume * masterVolume (for quick calculations)
|
||||
_masterVolume = p['x'] * p['x'],
|
||||
// Minimum frequency before stopping
|
||||
_minFreqency = p['g'],
|
||||
// If the phaser is active
|
||||
_phaser = p['q'] || p['r'],
|
||||
// Change in phase offset
|
||||
_phaserDeltaOffset = p['r'] * p['r'] * p['r'] * .2,
|
||||
// Phase offset for phaser effect
|
||||
_phaserOffset = p['q'] * p['q'] * (p['q'] < 0 ? -1020 : 1020),
|
||||
// Once the time reaches this limit, some of the iables are r
|
||||
_repeatLimit = p['p'] ? ((1 - p['p']) * (1 - p['p']) * 20000 | 0) + 32 : 0,
|
||||
// The punch factor (louder at begining of sustain)
|
||||
_sustainPunch = p['d'],
|
||||
// Amount to change the period of the wave by at the peak of the vibrato wave
|
||||
_vibratoAmplitude = p['j'] / 2,
|
||||
// Speed at which the vibrato phase moves
|
||||
_vibratoSpeed = p['k'] * p['k'] * .01,
|
||||
// The type of wave to generate
|
||||
_waveType = p['a'];
|
||||
|
||||
var _envelopeLength = _envelopeLength0, // Length of the current envelope stage
|
||||
_envelopeOverLength0 = 1 / _envelopeLength0, // (for quick calculations)
|
||||
_envelopeOverLength1 = 1 / _envelopeLength1, // (for quick calculations)
|
||||
_envelopeOverLength2 = 1 / _envelopeLength2; // (for quick calculations)
|
||||
|
||||
// Damping muliplier which restricts how fast the wave position can move
|
||||
var _lpFilterDamping = 5 / (1 + p['u'] * p['u'] * 20) * (.01 + _lpFilterCutoff);
|
||||
if (_lpFilterDamping > .8) {
|
||||
_lpFilterDamping = .8;
|
||||
}
|
||||
_lpFilterDamping = 1 - _lpFilterDamping;
|
||||
|
||||
var _finished = false, // If the sound has finished
|
||||
_envelopeStage = 0, // Current stage of the envelope (attack, sustain, decay, end)
|
||||
_envelopeTime = 0, // Current time through current enelope stage
|
||||
_envelopeVolume = 0, // Current volume of the envelope
|
||||
_hpFilterPos = 0, // Adjusted wave position after high-pass filter
|
||||
_lpFilterDeltaPos = 0, // Change in low-pass wave position, as allowed by the cutoff and damping
|
||||
_lpFilterOldPos, // Previous low-pass wave position
|
||||
_lpFilterPos = 0, // Adjusted wave position after low-pass filter
|
||||
_periodTemp, // Period modified by vibrato
|
||||
_phase = 0, // Phase through the wave
|
||||
_phaserInt, // Integer phaser offset, for bit maths
|
||||
_phaserPos = 0, // Position through the phaser buffer
|
||||
_pos, // Phase expresed as a Number from 0-1, used for fast sin approx
|
||||
_repeatTime = 0, // Counter for the repeats
|
||||
_sample, // Sub-sample calculated 8 times per actual sample, averaged out to get the super sample
|
||||
_superSample, // Actual sample writen to the wave
|
||||
_vibratoPhase = 0; // Phase through the vibrato sine wave
|
||||
|
||||
// Buffer of wave values used to create the out of phase second wave
|
||||
var _phaserBuffer = new Array(1024),
|
||||
// Buffer of random values used to generate noise
|
||||
_noiseBuffer = new Array(32);
|
||||
for (var i = _phaserBuffer.length; i--; ) {
|
||||
_phaserBuffer[i] = 0;
|
||||
}
|
||||
for (var i = _noiseBuffer.length; i--; ) {
|
||||
_noiseBuffer[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (_finished) {
|
||||
return i;
|
||||
}
|
||||
|
||||
// Repeats every _repeatLimit times, partially rting the sound parameters
|
||||
if (_repeatLimit) {
|
||||
if (++_repeatTime >= _repeatLimit) {
|
||||
_repeatTime = 0;
|
||||
this.r();
|
||||
}
|
||||
}
|
||||
|
||||
// If _changeLimit is reached, shifts the pitch
|
||||
if (_changeLimit) {
|
||||
if (++_changeTime >= _changeLimit) {
|
||||
_changeLimit = 0;
|
||||
_period *= _changeAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// Acccelerate and apply slide
|
||||
_slide += _deltaSlide;
|
||||
_period *= _slide;
|
||||
|
||||
// Checks for frequency getting too low, and stops the sound if a minFrequency was set
|
||||
if (_period > _maxPeriod) {
|
||||
_period = _maxPeriod;
|
||||
if (_minFreqency > 0) {
|
||||
_finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
_periodTemp = _period;
|
||||
|
||||
// Applies the vibrato effect
|
||||
if (_vibratoAmplitude > 0) {
|
||||
_vibratoPhase += _vibratoSpeed;
|
||||
_periodTemp *= 1 + Math.sin(_vibratoPhase) * _vibratoAmplitude;
|
||||
}
|
||||
|
||||
_periodTemp |= 0;
|
||||
if (_periodTemp < 8) {
|
||||
_periodTemp = 8;
|
||||
}
|
||||
|
||||
// Sweeps the square duty
|
||||
if (!_waveType) {
|
||||
_squareDuty += _dutySweep;
|
||||
if (_squareDuty < 0) {
|
||||
_squareDuty = 0;
|
||||
} else if (_squareDuty > .5) {
|
||||
_squareDuty = .5;
|
||||
}
|
||||
}
|
||||
|
||||
// Moves through the different stages of the volume envelope
|
||||
if (++_envelopeTime > _envelopeLength) {
|
||||
_envelopeTime = 0;
|
||||
|
||||
switch (++_envelopeStage) {
|
||||
case 1:
|
||||
_envelopeLength = _envelopeLength1;
|
||||
break;
|
||||
case 2:
|
||||
_envelopeLength = _envelopeLength2;
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the volume based on the position in the envelope
|
||||
switch (_envelopeStage) {
|
||||
case 0:
|
||||
_envelopeVolume = _envelopeTime * _envelopeOverLength0;
|
||||
break;
|
||||
case 1:
|
||||
_envelopeVolume = 1 + (1 - _envelopeTime * _envelopeOverLength1) * 2 * _sustainPunch;
|
||||
break;
|
||||
case 2:
|
||||
_envelopeVolume = 1 - _envelopeTime * _envelopeOverLength2;
|
||||
break;
|
||||
case 3:
|
||||
_envelopeVolume = 0;
|
||||
_finished = true;
|
||||
}
|
||||
|
||||
// Moves the phaser offset
|
||||
if (_phaser) {
|
||||
_phaserOffset += _phaserDeltaOffset;
|
||||
_phaserInt = _phaserOffset | 0;
|
||||
if (_phaserInt < 0) {
|
||||
_phaserInt = -_phaserInt;
|
||||
} else if (_phaserInt > 1023) {
|
||||
_phaserInt = 1023;
|
||||
}
|
||||
}
|
||||
|
||||
// Moves the high-pass filter cutoff
|
||||
if (_filters && _hpFilterDeltaCutoff) {
|
||||
_hpFilterCutoff *= _hpFilterDeltaCutoff;
|
||||
if (_hpFilterCutoff < .00001) {
|
||||
_hpFilterCutoff = .00001;
|
||||
} else if (_hpFilterCutoff > .1) {
|
||||
_hpFilterCutoff = .1;
|
||||
}
|
||||
}
|
||||
|
||||
_superSample = 0;
|
||||
for (var j = 8; j--; ) {
|
||||
// Cycles through the period
|
||||
_phase++;
|
||||
if (_phase >= _periodTemp) {
|
||||
_phase %= _periodTemp;
|
||||
|
||||
// Generates new random noise for this period
|
||||
if (_waveType == 3) {
|
||||
for (var n = _noiseBuffer.length; n--; ) {
|
||||
_noiseBuffer[n] = Math.random() * 2 - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the sample from the oscillator
|
||||
switch (_waveType) {
|
||||
case 0: // Square wave
|
||||
_sample = ((_phase / _periodTemp) < _squareDuty) ? .5 : -.5;
|
||||
break;
|
||||
case 1: // Saw wave
|
||||
_sample = 1 - _phase / _periodTemp * 2;
|
||||
break;
|
||||
case 2: // Sine wave (fast and accurate approx)
|
||||
_pos = _phase / _periodTemp;
|
||||
_pos = (_pos > .5 ? _pos - 1 : _pos) * 6.28318531;
|
||||
_sample = 1.27323954 * _pos + .405284735 * _pos * _pos * (_pos < 0 ? 1 : -1);
|
||||
_sample = .225 * ((_sample < 0 ? -1 : 1) * _sample * _sample - _sample) + _sample;
|
||||
break;
|
||||
case 3: // Noise
|
||||
_sample = _noiseBuffer[Math.abs(_phase * 32 / _periodTemp | 0)];
|
||||
}
|
||||
|
||||
// Applies the low and high pass filters
|
||||
if (_filters) {
|
||||
_lpFilterOldPos = _lpFilterPos;
|
||||
_lpFilterCutoff *= _lpFilterDeltaCutoff;
|
||||
if (_lpFilterCutoff < 0) {
|
||||
_lpFilterCutoff = 0;
|
||||
} else if (_lpFilterCutoff > .1) {
|
||||
_lpFilterCutoff = .1;
|
||||
}
|
||||
|
||||
if (_lpFilterOn) {
|
||||
_lpFilterDeltaPos += (_sample - _lpFilterPos) * _lpFilterCutoff;
|
||||
_lpFilterDeltaPos *= _lpFilterDamping;
|
||||
} else {
|
||||
_lpFilterPos = _sample;
|
||||
_lpFilterDeltaPos = 0;
|
||||
}
|
||||
|
||||
_lpFilterPos += _lpFilterDeltaPos;
|
||||
|
||||
_hpFilterPos += _lpFilterPos - _lpFilterOldPos;
|
||||
_hpFilterPos *= 1 - _hpFilterCutoff;
|
||||
_sample = _hpFilterPos;
|
||||
}
|
||||
|
||||
// Applies the phaser effect
|
||||
if (_phaser) {
|
||||
_phaserBuffer[_phaserPos % 1024] = _sample;
|
||||
_sample += _phaserBuffer[(_phaserPos - _phaserInt + 1024) % 1024];
|
||||
_phaserPos++;
|
||||
}
|
||||
|
||||
_superSample += _sample;
|
||||
}
|
||||
|
||||
// Averages out the super samples and applies volumes
|
||||
_superSample *= .125 * _envelopeVolume * _masterVolume;
|
||||
|
||||
// Clipping if too loud
|
||||
buffer[i] = _superSample >= 1 ? 32767 : _superSample <= -1 ? -32768 : _superSample * 32767 | 0;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from http://codebase.es/riffwave/
|
||||
var synth = new SfxrSynth();
|
||||
// Export for the Closure Compiler
|
||||
function jsfxr (settings, audioCtx, cb) {
|
||||
// Initialize SfxrParams
|
||||
synth._p.ss(settings);
|
||||
// Synthesize Wave
|
||||
var envelopeFullLength = synth.tr();
|
||||
var data = new Uint8Array(((envelopeFullLength + 1) / 2 | 0) * 4 + 44);
|
||||
|
||||
var used = synth.sw(new Uint16Array(data.buffer, 44), envelopeFullLength) * 2;
|
||||
|
||||
var dv = new Uint32Array(data.buffer, 0, 44);
|
||||
// Initialize header
|
||||
dv[0] = 0x46464952; // "RIFF"
|
||||
dv[1] = used + 36; // put total size here
|
||||
dv[2] = 0x45564157; // "WAVE"
|
||||
dv[3] = 0x20746D66; // "fmt "
|
||||
dv[4] = 0x00000010; // size of the following
|
||||
dv[5] = 0x00010001; // Mono: 1 channel, PCM format
|
||||
dv[6] = 0x0000AC44; // 44,100 samples per second
|
||||
dv[7] = 0x00015888; // byte rate: two bytes per sample
|
||||
dv[8] = 0x00100002; // 16 bits per sample, aligned on every two bytes
|
||||
dv[9] = 0x61746164; // "data"
|
||||
dv[10] = used; // put number of samples here
|
||||
|
||||
// Base64 encoding written by me, @maettig
|
||||
used += 44;
|
||||
var i = 0,
|
||||
base64Characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
||||
output = 'data:audio/wav;base64,';
|
||||
for (; i < used; i += 3)
|
||||
{
|
||||
var a = data[i] << 16 | data[i + 1] << 8 | data[i + 2];
|
||||
output += base64Characters[a >> 18] + base64Characters[a >> 12 & 63] + base64Characters[a >> 6 & 63] + base64Characters[a & 63];
|
||||
}
|
||||
|
||||
audioCtx && audioCtx.decodeAudioData(data.buffer, cb);
|
||||
|
||||
return output;
|
||||
}
|
||||
31
cookbook/src/examples/behindasteroids/behind-asteroids/src/lib/math.js
Executable file
@ -0,0 +1,31 @@
|
||||
|
||||
// normalize radian angle between -PI and PI (assuming it is not too far)
|
||||
function normAngle (a) {
|
||||
return a < -Math.PI ? a + 2*Math.PI :
|
||||
a>Math.PI ? a - 2*Math.PI : a;
|
||||
}
|
||||
|
||||
function smoothstep (min, max, value) {
|
||||
var x = Math.max(0, Math.min(1, (value-min)/(max-min)));
|
||||
return x*x*(3 - 2*x);
|
||||
}
|
||||
|
||||
function scoreTxt (s) {
|
||||
return (s<=9?"0":"")+s;
|
||||
}
|
||||
|
||||
function dist (a, b) {
|
||||
var x = a[0]-b[0];
|
||||
var y = a[1]-b[1];
|
||||
return Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
function length (v) {
|
||||
return Math.sqrt(v[0]*v[0]+v[1]*v[1]);
|
||||
}
|
||||
|
||||
function circleCollides (a, b, r) {
|
||||
var x = a[0] - b[0];
|
||||
var y = a[1] - b[1];
|
||||
return x*x+y*y < r*r;
|
||||
}
|
||||
16
cookbook/src/examples/behindasteroids/behind-asteroids/src/lib/path.js
Executable file
@ -0,0 +1,16 @@
|
||||
/* global ctx */
|
||||
|
||||
function path (pts, noclose) { // eslint-disable-line no-unused-vars
|
||||
ctx.beginPath();
|
||||
var mv = 1;
|
||||
for (var i = 0; pts && i<pts.length; ++i) {
|
||||
var p = pts[i];
|
||||
if (p) {
|
||||
if (mv) ctx.moveTo(p[0], p[1]);
|
||||
else ctx.lineTo(p[0], p[1]);
|
||||
mv = 0;
|
||||
}
|
||||
else mv = 1;
|
||||
}
|
||||
if (!noclose) ctx.closePath();
|
||||
}
|
||||
80
cookbook/src/examples/behindasteroids/behind-asteroids/src/lib/webgl.js
Executable file
@ -0,0 +1,80 @@
|
||||
/* global gl, W, H, DEBUG */
|
||||
|
||||
function glCreateShader (vert, frag) {
|
||||
var handle, type = gl.VERTEX_SHADER, src = vert;
|
||||
handle = gl.createShader(type);
|
||||
gl.shaderSource(handle, src);
|
||||
gl.compileShader(handle);
|
||||
var vertex = handle;
|
||||
|
||||
if (DEBUG) {
|
||||
if (!gl.getShaderParameter(handle, gl.COMPILE_STATUS))
|
||||
throw gl.getShaderInfoLog(handle);
|
||||
}
|
||||
|
||||
type = gl.FRAGMENT_SHADER;
|
||||
src = frag;
|
||||
handle = gl.createShader(type);
|
||||
gl.shaderSource(handle, src);
|
||||
gl.compileShader(handle);
|
||||
var fragment = handle;
|
||||
|
||||
if (DEBUG) {
|
||||
if (!gl.getShaderParameter(handle, gl.COMPILE_STATUS))
|
||||
throw gl.getShaderInfoLog(handle);
|
||||
}
|
||||
|
||||
var program = gl.createProgram();
|
||||
gl.attachShader(program, vertex);
|
||||
gl.attachShader(program, fragment);
|
||||
gl.linkProgram(program);
|
||||
|
||||
if (DEBUG) {
|
||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
|
||||
throw gl.getProgramInfoLog(program);
|
||||
}
|
||||
|
||||
gl.useProgram(program);
|
||||
var p = gl.getAttribLocation(program, "p");
|
||||
gl.enableVertexAttribArray(p);
|
||||
gl.vertexAttribPointer(p, 2, gl.FLOAT, false, 0, 0);
|
||||
return [program];
|
||||
}
|
||||
function glBindShader (shader) {
|
||||
gl.useProgram(shader[0]);
|
||||
}
|
||||
function glUniformLocation(shader, name) {
|
||||
return shader[name] || (shader[name] = gl.getUniformLocation(shader[0], name));
|
||||
}
|
||||
function glCreateTexture () {
|
||||
var tex = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, tex);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
return tex;
|
||||
}
|
||||
function glSetTexture (t, value) {
|
||||
gl.bindTexture(gl.TEXTURE_2D, t);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, value);
|
||||
}
|
||||
function glBindTexture (t, unit) {
|
||||
gl.activeTexture(gl.TEXTURE0 + unit);
|
||||
gl.bindTexture(gl.TEXTURE_2D, t);
|
||||
return unit;
|
||||
}
|
||||
function glCreateFBO () {
|
||||
var handle = gl.createFramebuffer();
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, handle);
|
||||
var color = glCreateTexture();
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, W, H, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, color, 0);
|
||||
return [handle, color];
|
||||
}
|
||||
function glBindFBO (fbo) {
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[0]);
|
||||
}
|
||||
function glGetFBOTexture (fbo) {
|
||||
return fbo[1];
|
||||
}
|
||||
29
cookbook/src/examples/behindasteroids/behind-asteroids/src/particles.js
Executable file
@ -0,0 +1,29 @@
|
||||
/* global
|
||||
particles play Aexplosion1 Aexplosion2 ctx
|
||||
*/
|
||||
function explose (o) {
|
||||
play(Math.random()<0.5 ? Aexplosion1 : Aexplosion2);
|
||||
var n = Math.floor(19 + 9 * Math.random());
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var l = 30 * Math.random() - 10;
|
||||
var a = (Math.random() + 2 * Math.PI * i) / n;
|
||||
particles.push([
|
||||
o[0] + l * Math.cos(a),
|
||||
o[1] + l * Math.sin(a),
|
||||
a,
|
||||
0.06,
|
||||
Math.random()<0.3 ? 0 : 1000
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
|
||||
|
||||
function drawParticle () {
|
||||
ctx.globalAlpha = 0.8;
|
||||
ctx.fillStyle = "#f00";
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 1, 0, 2*Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
32
cookbook/src/examples/behindasteroids/behind-asteroids/src/post.js
Executable file
@ -0,0 +1,32 @@
|
||||
|
||||
return {
|
||||
dispose: function(){
|
||||
cancelAnimationFrame(raf);
|
||||
},
|
||||
getWebGLParams: function () {
|
||||
var pt = playingSince / 1000;
|
||||
var pl = player;
|
||||
var ex = gameOver || excitementSmoothed;
|
||||
var J = jumping;
|
||||
var P = playingSince<0 || gameOver || dying ? 0 : 1;
|
||||
var s = !player ? smoothstep(-4000, -3000, playingSince) : 1;
|
||||
var F = smoothstep(300, 0, t-lastLoseShot) +
|
||||
!gameOver && lifes>4 ? 0.5 * smoothstep(-1, 1, Math.cos(0.01*t)) : 0;
|
||||
var k = [ shaking[0], shaking[1] ];
|
||||
return {
|
||||
pt: pt,
|
||||
pl: pl,
|
||||
ex: ex,
|
||||
J: J,
|
||||
P: P,
|
||||
s: s,
|
||||
F: F,
|
||||
k: k,
|
||||
W: W,
|
||||
H: H,
|
||||
S: SEED
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
3
cookbook/src/examples/behindasteroids/behind-asteroids/src/pre.js
Executable file
@ -0,0 +1,3 @@
|
||||
/* eslint-disable */
|
||||
module.exports = (function(d,g,RENDER_CB){
|
||||
var raf;
|
||||
37
cookbook/src/examples/behindasteroids/behind-asteroids/src/setup.js
Executable file
@ -0,0 +1,37 @@
|
||||
/* global g d MOBILE
|
||||
gameScale: true
|
||||
glCreateFBO glCreateShader glCreateTexture glUniformLocation
|
||||
STATIC_VERT
|
||||
BLUR1D_FRAG
|
||||
COPY_FRAG
|
||||
GAME_FRAG
|
||||
GLARE_FRAG
|
||||
LASER_FRAG
|
||||
PERSISTENCE_FRAG
|
||||
PLAYER_FRAG
|
||||
*/
|
||||
|
||||
var ctx,
|
||||
gameCtx = g.getContext("2d"),
|
||||
FW = MOBILE ? 480 : 800,
|
||||
FH = MOBILE ? 660 : 680,
|
||||
GAME_MARGIN = MOBILE ? 50 : 120,
|
||||
GAME_Y_MARGIN = MOBILE ? 140 : GAME_MARGIN,
|
||||
GAME_INC_PADDING = MOBILE ? 40 : 80,
|
||||
W = FW - 2 * GAME_MARGIN,
|
||||
H = FH - 2 * GAME_Y_MARGIN,
|
||||
borderLength = 2*(W+H+2*GAME_INC_PADDING),
|
||||
SEED = Math.random();
|
||||
|
||||
// DOM setup
|
||||
|
||||
d.style.webkitTransformOrigin = d.style.transformOrigin = "0 0";
|
||||
|
||||
g.width = W;
|
||||
g.height = H;
|
||||
|
||||
var uiScale = 1;
|
||||
|
||||
function checkSize () {
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform vec2 dim;
|
||||
uniform vec2 dir;
|
||||
|
||||
void main() {
|
||||
vec4 color = vec4(0.0);
|
||||
vec2 off1 = vec2(1.3846153846) * dir;
|
||||
vec2 off2 = vec2(3.2307692308) * dir;
|
||||
color += texture2D(t, uv) * 0.2270270270;
|
||||
color += texture2D(t, uv + (off1 / dim)) * 0.3162162162;
|
||||
color += texture2D(t, uv - (off1 / dim)) * 0.3162162162;
|
||||
color += texture2D(t, uv + (off2 / dim)) * 0.0702702703;
|
||||
color += texture2D(t, uv - (off2 / dim)) * 0.0702702703;
|
||||
gl_FragColor = color;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = texture2D(t, uv);
|
||||
}
|
||||
46
cookbook/src/examples/behindasteroids/behind-asteroids/src/shaders/game.frag
Executable file
@ -0,0 +1,46 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
uniform sampler2D G; // game
|
||||
uniform sampler2D R; // persistence
|
||||
uniform sampler2D B; // blur
|
||||
uniform sampler2D L; // glare
|
||||
uniform sampler2D E; // env (player)
|
||||
uniform float s; // starting
|
||||
uniform float F; // fail factor (red effect)
|
||||
uniform vec2 k;
|
||||
|
||||
float squircleDist (vec2 a, vec2 b) {
|
||||
float p = 10.0;
|
||||
vec2 c = a-b;
|
||||
return pow(abs(pow(abs(c.x), p)+pow(abs(c.y), p)), 1.0/p);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 UV = uv + k;
|
||||
vec2 pos = (UV/0.98)-0.01;
|
||||
float d = squircleDist(UV, vec2(0.5));
|
||||
float dd = smoothstep(0.45, 0.51, d);
|
||||
pos = mix(pos, vec2(0.5), 0.2 * (0.6 - d) - 0.02 * d);
|
||||
|
||||
vec3 gc = texture2D(G, pos).rgb;
|
||||
|
||||
gl_FragColor =
|
||||
step(0.0, UV.x) *
|
||||
step(UV.x, 1.0) *
|
||||
step(0.0, UV.y) *
|
||||
step(UV.y, 1.0) *
|
||||
vec4((
|
||||
vec3(0.03 + 0.1 * F, 0.04, 0.05) +
|
||||
mix(vec3(0.05, 0.1, 0.15) - gc, 2.0 * gc, s) +
|
||||
s * (
|
||||
texture2D(L, pos).rgb +
|
||||
vec3(0.3 + F, 0.6, 1.0) * (
|
||||
texture2D(R, pos).rgb +
|
||||
3.0 * texture2D(B, pos).rgb
|
||||
) +
|
||||
0.5 * texture2D(E, pos).rgb
|
||||
)
|
||||
)
|
||||
* mix(1.0, smoothstep(1.0, 0.0, dd), 0.6), 1.0);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = vec4(step(0.9, texture2D(t, uv).r));
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
|
||||
void main() {
|
||||
vec3 c = texture2D(t, uv).rgb;
|
||||
vec2 off = 0.003 * vec2(
|
||||
cos(47.0 * uv.y),
|
||||
sin(67.0 * uv.x)
|
||||
);
|
||||
gl_FragColor = vec4(
|
||||
c.r + c.g + c.b + texture2D(t, uv+off).b
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform sampler2D r;
|
||||
|
||||
void main() {
|
||||
vec3 b = texture2D(r, uv).rgb;
|
||||
gl_FragColor = vec4(
|
||||
b * (0.82 - 0.3 * b.r * b.r) +
|
||||
texture2D(t, uv).rgb,
|
||||
1.0);
|
||||
}
|
||||
107
cookbook/src/examples/behindasteroids/behind-asteroids/src/shaders/player.frag
Executable file
@ -0,0 +1,107 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec2 uv;
|
||||
|
||||
uniform float pt; // playing since time
|
||||
uniform float pl; // player number
|
||||
uniform float S; // Seed
|
||||
uniform float ex; // excitement
|
||||
uniform float J; // jump
|
||||
uniform float P; // playing
|
||||
|
||||
float disc (vec2 c, vec2 r) {
|
||||
return step(length((uv - c) / r), 1.0);
|
||||
}
|
||||
float squircle (vec2 c, vec2 r, float p) {
|
||||
vec2 v = (uv - c) / r;
|
||||
return step(pow(abs(v.x), p) + pow(abs(v.y), p), 1.0);
|
||||
}
|
||||
|
||||
vec3 env () {
|
||||
return 0.1 +
|
||||
0.3 * vec3(1.0, 0.9, 0.7) * smoothstep(0.4, 0.1, distance(uv, vec2(0.2, 1.2))) +
|
||||
0.4 * vec3(0.8, 0.6, 1.0) * smoothstep(0.5, 0.2, distance(uv, vec2(1.3, 0.7)));
|
||||
}
|
||||
|
||||
vec4 player (float p, float dx) {
|
||||
vec4 c = vec4(0.0);
|
||||
|
||||
vec2 e = vec2(
|
||||
min(ex, 1.0),
|
||||
mix(min(ex, 1.0), min(ex-1.0, 1.0), 0.5));
|
||||
|
||||
// variable params
|
||||
vec4 skin = 0.2 + 0.4 * pow(abs(cos(4.*p+S)), 2.0) * vec4(1.0, 0.7, 0.3, 1.0);
|
||||
vec4 hair = vec4(0.5, 0.3, 0.3, 1.0);
|
||||
vec4 sweater = vec4(
|
||||
0.3 * (1.0 + cos(3.*p + 6.*S)),
|
||||
0.2 * (1.0 + cos(7.*p + 7.*S)),
|
||||
0.1+0.2 * (1.0 + sin(7.*p + 8.*S)),
|
||||
1.0);
|
||||
float feminity = step(sin(9.0*p+S), 0.0);
|
||||
float hairSize = 0.02 + 0.02 * feminity * cos(p+S);
|
||||
float walk = step(dx, -0.01) + step(0.01, dx);
|
||||
float play = (1.0 - walk) * step(0.0, pt);
|
||||
vec2 pos = vec2(0.5) +
|
||||
// jumping cycle
|
||||
J * vec2(0.0, 0.2) +
|
||||
// walking cycle
|
||||
walk * vec2(
|
||||
0.03 * cos(4.0*pt + sin(pt)),
|
||||
0.05 * abs(sin(3.0*pt))) +
|
||||
// playing cycle
|
||||
e * play * (1.0 - P) * vec2(
|
||||
0.05 * cos(pt * (1.0 + 0.1 * sin(pt))),
|
||||
0.05 * abs(sin(pt)));
|
||||
vec2 pos2 = mix(pos, vec2(0.5), 0.5);
|
||||
pos.x += dx;
|
||||
pos2.x += dx;
|
||||
|
||||
// face skin
|
||||
c += skin * disc(pos, vec2(0.06, 0.1));
|
||||
// mouth
|
||||
c *= 1.0 - (0.5 + 0.5 * feminity) * disc(pos - vec2(0.0, 0.04), vec2(0.03, 0.01));
|
||||
// left eye
|
||||
c *= 1.0 - disc(pos + vec2(0.03, 0.03), vec2(0.02, 0.01));
|
||||
// right eye
|
||||
c *= 1.0 - disc(pos + vec2(-0.03, 0.03), vec2(0.02, 0.01));
|
||||
// nose
|
||||
c *= 1.0 - 0.6 * disc(pos, vec2(0.01, 0.02));
|
||||
// hair (also contrib to face skin color)
|
||||
c += hair * disc(pos + vec2(0.0, hairSize), vec2(0.07, 0.1 + hairSize));
|
||||
// left hand
|
||||
c += play * (hair + skin) * disc(pos2 - vec2(
|
||||
-0.2 + 0.01 * cos(5.0*pt),
|
||||
0.45 - 0.1 * e.y * step(0.0, pt) * P * pow(abs(sin(8.0 * pt * (1.0 + 0.2 * cos(pt)))), 4.0)
|
||||
), vec2(0.055, 0.05));
|
||||
// right hand
|
||||
c += play * (hair + skin) * disc(pos2 - vec2(
|
||||
0.2 + 0.01 * cos(5.0*pt),
|
||||
0.45 - 0.1 * e.x * step(2.0, pt) * P * pow(abs(cos(7.0 * pt)), 4.0)
|
||||
), vec2(0.055, 0.05));
|
||||
// neck
|
||||
c += step(c.a, 0.0) * (hair + skin) *
|
||||
squircle(pos - vec2(0.0, 0.10 + 0.02 * feminity),
|
||||
vec2(0.05 - 0.01 * feminity, 0.03), 4.0);
|
||||
// sweater
|
||||
vec2 sr = vec2(
|
||||
0.16 + 0.04 * sin(9.*p),
|
||||
0.27 + 0.02 * cos(9.*p));
|
||||
c += step(c.r+c.g+c.b, 0.0) * sweater * step(1.0,
|
||||
squircle(pos - vec2(0.0, 0.35), sr * (1.0 - 0.1 * feminity), 4.0) +
|
||||
disc(pos - vec2(0.0, 0.35), sr));
|
||||
return c;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float light = 0.6 + 0.4 * smoothstep(2.0, 0.0, distance(pt, -2.0));
|
||||
vec4 c = vec4(0.0);
|
||||
// main player
|
||||
c += (1.0 - smoothstep(-0.0, -5.0, pt)) *
|
||||
player(pl+step(pt, 0.0), -0.6 * smoothstep(-1., -5., pt));
|
||||
// prev player
|
||||
c += step (1.0, pl) *
|
||||
player(pl+step(pt, 0.0)-1.0, 2.0 *smoothstep(-4., -1., pt));
|
||||
c *= 1.0 - 1.3 * distance(uv, vec2(0.5));
|
||||
gl_FragColor = vec4(light * mix(env(), c.rgb, clamp(c.a, 0.0, 1.0)), 1.0);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
attribute vec2 p;
|
||||
varying vec2 uv;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(p,0.0,1.0);
|
||||
uv = 0.5 * (p+1.0);
|
||||
}
|
||||
20
cookbook/src/examples/behindasteroids/behind-asteroids/src/sounds.js
Executable file
@ -0,0 +1,20 @@
|
||||
/* global audio */
|
||||
|
||||
var Ashot = audio([0,0.06,0.18,,0.33,0.5,0.23,-0.04,-0.24,,,-0.02,,0.37,-0.22,,,,0.8,,,,,0.3]),
|
||||
|
||||
Amusic1 = audio([,,0.12,,0.13,0.16,,,,,,,,,,,,,0.7,,,,,0.5]),
|
||||
Amusic2 = audio([,,0.12,,0.13,0.165,,,,,,,,,,,,,0.7,,,,,0.5]),
|
||||
|
||||
Aexplosion1 = audio([3,,0.35,0.5369,0.5,0.15,,-0.02,,,,-0.7444,0.78,,,0.7619,,,0.1,,,,,0.5]),
|
||||
Aexplosion2 = audio([3,,0.38,0.5369,0.52,0.18,,-0.02,,,,-0.7444,0.78,,,0.7619,,,0.1,,,,,0.5]),
|
||||
|
||||
Asend = audio([2,0.07,0.04,,0.24,0.25,,0.34,-0.1999,,,-0.02,,0.3187,,,-0.14,0.04,0.85,,0.28,0.63,,0.5]),
|
||||
AsendFail = audio([1,,0.04,,0.45,0.14,0.06,-0.06,0.02,0.87,0.95,-0.02,,0.319,,,-0.14,0.04,0.5,,,,,0.4]),
|
||||
|
||||
Alost = audio([0,0.11,0.37,,0.92,0.15,,-0.06,-0.04,0.3,0.14,0.1,,0.5047,,,,,0.16,-0.02,,0.5,,1]),
|
||||
Aleave = audio([0,0.11,0.36,,0.66,0.19,,0.06,-0.06,0.05,0.8,-0.12,0.3,0.19,-0.06,,,-0.02,0.23,-0.02,,0.4,,0.4]),
|
||||
Acoin = audio([0,,0.094,0.29,0.42,0.563,,,,,,0.4399,0.5658,,,,,,1,,,,,0.5]),
|
||||
Amsg = audio([2,0.07,0.1,,0.2,0.75,0.35,-0.1,0.12,,,-0.02,,,,,-0.06,-0.0377,0.26,,,0.8,,0.7]),
|
||||
Aufo = audio([2,0.05,0.74,,0.33,0.5,,,,0.46,0.29,,,,,,,,1,,,,,0.3]),
|
||||
Alife = audio([0,0.12,0.8,0.48,0.77,0.92,,-0.12,-0.0999,,,-0.4,0.2,0.34,,0.65,,,0.93,-0.02,,,,0.38]),
|
||||
Ajump = audio([3,,0.12,0.56,0.27,0.07,,-0.12,0.02,,,-0.02,0.68,,,,-0.04,-0.022,0.06,,,0.06,,0.5]);
|
||||
96
cookbook/src/examples/behindasteroids/behind-asteroids/src/spaceship.js
Executable file
@ -0,0 +1,96 @@
|
||||
/* global
|
||||
ctx t path lifes play Alost AIboostSmoothed dying:true deads:true achievements killSmoothed:true
|
||||
*/
|
||||
|
||||
function spaceshipDie() {
|
||||
if (dying) return;
|
||||
dying = t;
|
||||
if (lifes == 1) {
|
||||
play(Alost);
|
||||
}
|
||||
deads ++;
|
||||
achievements[1] ++;
|
||||
killSmoothed ++;
|
||||
}
|
||||
|
||||
/*
|
||||
function resetSpaceship () {
|
||||
var x = W * (0.25 + 0.5 * Math.random());
|
||||
var y = H * (0.25 + 0.5 * Math.random());
|
||||
spaceship = [x, y, 0, 0];
|
||||
}
|
||||
*/
|
||||
|
||||
// RENDERING
|
||||
|
||||
function drawSpaceship (o) {
|
||||
ctx.strokeStyle = "#f00";
|
||||
ctx.globalAlpha = 0.4;
|
||||
ctx.rotate(o[4]);
|
||||
if (dying) {
|
||||
ctx.lineWidth = 2;
|
||||
var delta = (t-dying)/200;
|
||||
|
||||
path([
|
||||
[-6, -6 - 0.5*delta],
|
||||
[3, -3 - 0.9*delta]
|
||||
]);
|
||||
ctx.stroke();
|
||||
|
||||
if (delta < 8) {
|
||||
path([
|
||||
[3 + 0.4*delta, -3 - 0.8*delta],
|
||||
[12 + 0.4*delta, 0 - 0.5*delta]
|
||||
]);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
path([
|
||||
[12, 0+0.4*delta],
|
||||
[3, 3+delta]
|
||||
]);
|
||||
ctx.stroke();
|
||||
|
||||
if (delta < 9) {
|
||||
path([
|
||||
[1, 5 + delta],
|
||||
[-6, 6 + delta]
|
||||
]);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (delta < 7) {
|
||||
path([
|
||||
[-6 - delta, -6],
|
||||
[-6 - delta, 6]
|
||||
]);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
else {
|
||||
path([
|
||||
[-6, -6],
|
||||
[ 12, 0],
|
||||
[ -6, 6],
|
||||
[ -5, 0]
|
||||
]);
|
||||
ctx.stroke();
|
||||
if (AIboostSmoothed>0.2) {
|
||||
path([
|
||||
[-7, 2*Math.random()-1],
|
||||
[-7 - 5*AIboostSmoothed, 4*Math.random()-2]
|
||||
]);
|
||||
ctx.stroke();
|
||||
}
|
||||
if (AIboostSmoothed<-0.2) {
|
||||
path([
|
||||
[2, -5],
|
||||
[2 - 5 * AIboostSmoothed, -7],
|
||||
,
|
||||
[2, 5],
|
||||
[2 - 5 * AIboostSmoothed, 7]
|
||||
]);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
cookbook/src/examples/behindasteroids/behind-asteroids/src/state.js
Executable file
@ -0,0 +1,69 @@
|
||||
/* global W H */
|
||||
|
||||
// N.B: constants don't live here
|
||||
|
||||
var t = 0, dt,
|
||||
|
||||
spaceship = [ W/2, H/2, 0, 0, 0 ], // [x, y, velx, vely, rot]
|
||||
asteroids = [], // array of [x, y, rot, vel, shape, lvl]
|
||||
ufos = [], // array of [x, y, vx, vy, timeBeforeShot]
|
||||
bullets = [], // array of [x, y, velx, vely, life, isAlien]
|
||||
incomingObjects = [], // array of: [pos, vel, ang, force, rotVel, shape, lvl, key, rotAmp, rotAmpValid, explodeTime]
|
||||
particles = [], // array of [x, y, rot, vel, life]
|
||||
|
||||
dying = 0,
|
||||
resurrectionTime = 0,
|
||||
best = 0,
|
||||
score = 0, // current asteroids player score
|
||||
scoreForLife, // will track the next score to win a life (10000, 20000, ...)
|
||||
playingSince = -10000,
|
||||
deads = 0,
|
||||
player = 0,
|
||||
lifes = 0,
|
||||
|
||||
AIshoot = 0, AIboost = 0, AIrotate = 0, AIexcitement = 0,
|
||||
AIboostSmoothed = 0,
|
||||
|
||||
shaking = [0,0],
|
||||
jumping = 0,
|
||||
jumpingFreq = 0,
|
||||
jumpingPhase = 0,
|
||||
jumpingFreqSmoothed = 0,
|
||||
jumpingAmp = 0,
|
||||
jumpingAmpSmoothed = 0,
|
||||
killSmoothed = 0,
|
||||
|
||||
musicPhase = 0,
|
||||
musicTick = 0,
|
||||
musicPaused = 0,
|
||||
ufoMusicTime = 0,
|
||||
|
||||
excitementSmoothed = 0,
|
||||
neverPlayed = 1,
|
||||
neverUFOs = 1,
|
||||
combos = 0,
|
||||
combosTarget,
|
||||
gameOver,
|
||||
awaitingContinue = 0,//localStorage.ba_pl && parseInt(localStorage.ba_pl),
|
||||
// achievements: [nbAsteroids, nbKills, nbUfos]
|
||||
achievements,
|
||||
|
||||
lastScoreIncrement = 0,
|
||||
lastJump = 0,
|
||||
lastBulletShoot = 0,
|
||||
lastExtraLife = 0,
|
||||
lastLoseShot = 0,
|
||||
|
||||
// Input state : updated by user events, handled & emptied by the update loop
|
||||
keys = {},
|
||||
tap,
|
||||
|
||||
// variables related to setup
|
||||
gameScale;
|
||||
|
||||
|
||||
function helpVisible () {
|
||||
return neverPlayed &&
|
||||
incomingObjects[0] &&
|
||||
playingSince>8000;
|
||||
}
|
||||
29
cookbook/src/examples/behindasteroids/behind-asteroids/src/target.html
Executable file
@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<title>Behind Asteroids</title>
|
||||
<meta name=viewport content="width=480, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
background: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#d {
|
||||
position: absolute;
|
||||
}
|
||||
canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="d"><canvas id="c"></canvas><canvas id="u"></canvas><canvas id="g" hidden></canvas></div>
|
||||
<script src="b.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
56
cookbook/src/examples/behindasteroids/behind-asteroids/src/ufo.js
Executable file
@ -0,0 +1,56 @@
|
||||
/* global
|
||||
dt dying spaceship shoot ctx path
|
||||
*/
|
||||
|
||||
function applyUFOlogic (o) {
|
||||
o[4] -= dt;
|
||||
if (o[4]<0) {
|
||||
o[4] = 500 + 300 * Math.random();
|
||||
if (!dying) {
|
||||
var target = Math.atan2(spaceship[1] - o[1], spaceship[0] - o[0]);
|
||||
if (!o[2] || Math.random()<0.2) {
|
||||
var randomAngle = 2*Math.PI*Math.random();
|
||||
o[2] = 0.08 * Math.cos(randomAngle);
|
||||
o[3] = 0.08 * Math.sin(randomAngle);
|
||||
}
|
||||
shoot(o, 0.3+0.1*Math.random(), target + 0.6 * Math.random() - 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RENDERING
|
||||
|
||||
var UFOa = [
|
||||
[8,0],
|
||||
[7,5],
|
||||
[0,9],
|
||||
[7,14]
|
||||
];
|
||||
var UFOb = [
|
||||
[15,14],
|
||||
[22,9],
|
||||
[15,5],
|
||||
[14,0]
|
||||
];
|
||||
|
||||
var UFO =
|
||||
UFOa
|
||||
.concat(UFOb)
|
||||
.concat(UFOa)
|
||||
.concat([,])
|
||||
.concat(UFOb)
|
||||
.concat([
|
||||
,
|
||||
[7,5],
|
||||
[15,5],
|
||||
,
|
||||
[0,9],
|
||||
[22,9]
|
||||
]);
|
||||
|
||||
function drawUFO () {
|
||||
ctx.globalAlpha = 0.4;
|
||||
ctx.strokeStyle = "#f00";
|
||||
path(UFO);
|
||||
ctx.stroke();
|
||||
}
|
||||
430
cookbook/src/examples/behindasteroids/behind-asteroids/src/ui.js
Executable file
@ -0,0 +1,430 @@
|
||||
/* global
|
||||
ctx path font gameOver W H player playingSince:true awaitingContinue scoreTxt
|
||||
lifes dying MOBILE best score t UFO neverPlayed lastExtraLife neverUFOs dt play
|
||||
Amsg GAME_MARGIN FW FH combos achievements musicTick helpVisible */
|
||||
|
||||
// IN GAME UI
|
||||
|
||||
function button (t1, t2) {
|
||||
ctx.globalAlpha = 1;
|
||||
path([
|
||||
[0, 0],
|
||||
[160, 0],
|
||||
[160, 120],
|
||||
[0, 120]
|
||||
]);
|
||||
ctx.translate(80, 30);
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fill();
|
||||
ctx.save();
|
||||
font(t1, 2);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(0, 40);
|
||||
font(t2, 2);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawGameUI () {
|
||||
ctx.save();
|
||||
ctx.fillStyle = ctx.strokeStyle = "#0f0";
|
||||
ctx.globalAlpha = 0.3;
|
||||
|
||||
if (gameOver) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = "#0f0";
|
||||
ctx.globalAlpha = 0.3;
|
||||
ctx.save();
|
||||
ctx.translate((W-340)/2, H/8);
|
||||
font("YOU EARNED ", 2, 1);
|
||||
ctx.globalAlpha = 0.5;
|
||||
font((player*25)+"¢", 2, 1);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(W/2, H/4);
|
||||
font("FROM "+player+" PLAYERS", 2);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.5;
|
||||
ctx.translate((W-200)/2, H/2);
|
||||
drawAchievements(2);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(W/2 - 180, H - 160);
|
||||
button("TWEET", "SCORE");
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(W/2 + 20, H - 160);
|
||||
button("PLAY", "AGAIN");
|
||||
ctx.restore();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
else if (playingSince < 0 || awaitingContinue) {
|
||||
ctx.save();
|
||||
ctx.translate(W-50, 20);
|
||||
font(scoreTxt(0), 1.5, -1);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(50, 70);
|
||||
if ((!awaitingContinue || playingSince>0) && t%1000<500)
|
||||
font("PLAYER "+(awaitingContinue||player+1), 2, 1);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(W/2 - 160, 0.7*H);
|
||||
path([
|
||||
[0,2],
|
||||
[0,18]
|
||||
]);
|
||||
ctx.stroke();
|
||||
ctx.translate(40,0);
|
||||
font("COIN", 2, 1);
|
||||
ctx.translate(40,0);
|
||||
path([
|
||||
[0,2],
|
||||
[0,18]
|
||||
]);
|
||||
ctx.stroke();
|
||||
ctx.translate(40,0);
|
||||
font("PLAY", 2, 1);
|
||||
ctx.restore();
|
||||
}
|
||||
else {
|
||||
for (var i=1; i<lifes; i++) {
|
||||
ctx.save();
|
||||
ctx.translate(60 + i * 10, 50);
|
||||
ctx.rotate(-Math.PI/2);
|
||||
path([
|
||||
[-4, -4],
|
||||
[ 10, 0],
|
||||
[ -4, 4],
|
||||
[ -3, 0]
|
||||
]);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
if (!gameOver && dying && lifes==1) {
|
||||
ctx.save();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.translate(W/2, 140);
|
||||
font("GAME OVER", 2);
|
||||
ctx.restore();
|
||||
}
|
||||
if (!gameOver && awaitingContinue && playingSince > 0) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.translate(W/2, 140);
|
||||
font("CONTINUE ?", 3);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.translate(W/4, 210);
|
||||
font("YES", MOBILE ? 4 : 6);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.translate(3*W/4, 210);
|
||||
font("NO", MOBILE ? 4 : 6);
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.save();
|
||||
ctx.translate(W/2, H-14);
|
||||
font("2015 GREWEB INC", .6);
|
||||
ctx.restore();
|
||||
|
||||
if (!gameOver) {
|
||||
ctx.save();
|
||||
ctx.translate(W/2, 20);
|
||||
font(scoreTxt(best), .6);
|
||||
ctx.restore();
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(50, 20);
|
||||
font(scoreTxt(score), 1.5, 1);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
if (gameOver || playingSince<0 && t%1000<800) {
|
||||
ctx.save();
|
||||
ctx.translate(W-20, H-24);
|
||||
font(MOBILE ? "MOBILE" : "DESKTOP", .6, -1);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.translate(W-20, H-14);
|
||||
font("VERSION", .6, -1);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawGlitch () {
|
||||
ctx.save();
|
||||
ctx.fillStyle =
|
||||
ctx.strokeStyle = "#f00";
|
||||
ctx.globalAlpha = 0.03;
|
||||
ctx.translate(W/2, H/2);
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 4, 0, 2*Math.PI);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 12, 0, 2*Math.PI);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 12, 4, 6);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 12, 1, 2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
// EXTERNAL UI
|
||||
|
||||
var badgesIcons = [
|
||||
[
|
||||
[-11, -11],
|
||||
[4, -13],
|
||||
[6, -6],
|
||||
[14, 0],
|
||||
[14, 8],
|
||||
[6, 8],
|
||||
[-6, 14],
|
||||
[-14, 0]
|
||||
],
|
||||
[
|
||||
[-8, 13],
|
||||
[0, -13],
|
||||
[8, 13],
|
||||
[0, 11],
|
||||
[-8, 13],
|
||||
,
|
||||
[-10, -2],
|
||||
[10, 2],
|
||||
,
|
||||
[10, -2],
|
||||
[-10, 2],
|
||||
,
|
||||
],
|
||||
UFO.map(function (p) {
|
||||
return p ? [p[0]-11,p[1]-7] : p;
|
||||
})
|
||||
];
|
||||
|
||||
var lastStatement, lastStatementTime = 0;
|
||||
|
||||
var lastMessage2;
|
||||
|
||||
function drawUI () {
|
||||
var currentMessage = "",
|
||||
currentMessage2 = "",
|
||||
currentMessageClr = "#f7c",
|
||||
currentMessageClr2 = "#7fc";
|
||||
|
||||
function announcePlayer (player) {
|
||||
currentMessage = "PLAYER "+player;
|
||||
currentMessage2 = [
|
||||
"GENIOUS PLAYER!!",
|
||||
"EXPERIENCED PLAYER!!",
|
||||
"GOOD PLAYER. GET READY",
|
||||
"NICE PLAYER.",
|
||||
"BEGINNER.",
|
||||
"VERY BEGINNER. EASY KILL"
|
||||
][Math.floor(Math.exp((-player)/8)*6)];
|
||||
}
|
||||
|
||||
if (gameOver) {
|
||||
currentMessage = "PLAYER MASTERED THE GAME";
|
||||
currentMessage2 = "REACHED ᐃᐃᐃᐃᐃ";
|
||||
}
|
||||
else if (!player) {
|
||||
if (playingSince<-7000) {
|
||||
currentMessage = "BEHIND ASTEROIDS";
|
||||
currentMessage2 = "THE DARK SIDE";
|
||||
}
|
||||
else if (playingSince<-3500) {
|
||||
currentMessageClr = currentMessageClr2 = "#7cf";
|
||||
currentMessage = "SEND ASTEROIDS TO MAKE";
|
||||
currentMessage2 = "PLAYERS WASTE THEIR MONEY";
|
||||
}
|
||||
else if (!awaitingContinue) {
|
||||
var nb = Math.min(25, Math.floor((playingSince+3500)/80));
|
||||
for (var i=0; i<nb; i++)
|
||||
currentMessage += ".";
|
||||
if (playingSince>-2000)
|
||||
currentMessage2 = "A NEW PLAYER!";
|
||||
}
|
||||
else {
|
||||
if (playingSince<0) playingSince = 0; // jump to skip the "player coming"
|
||||
announcePlayer(awaitingContinue);
|
||||
}
|
||||
}
|
||||
else if (dying) {
|
||||
if (lifes==1) {
|
||||
currentMessageClr2 = "#f66";
|
||||
currentMessage = "GOOD JOB !!!";
|
||||
currentMessage2 = "THE DUDE IS BROKE";
|
||||
}
|
||||
else if (lifes==2) {
|
||||
currentMessageClr2 = "#f66";
|
||||
currentMessage = "OK...";
|
||||
currentMessage2 = "ONE MORE TIME !";
|
||||
}
|
||||
else {
|
||||
if (lastStatement && t - lastStatementTime > 3000) { // lastStatementTime is not used here
|
||||
currentMessage = lastStatement;
|
||||
}
|
||||
else {
|
||||
currentMessage = ["!!!", "GREAT!", "COOL!", "OMG!", "AHAH!", "RUDE!", "EPIC!", "WICKED!", "SHAME!", "HEHEHE!", "BWAHAHA!"];
|
||||
lastStatement = currentMessage = currentMessage[Math.floor(Math.random() * currentMessage.length)];
|
||||
lastStatementTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (playingSince<0) {
|
||||
currentMessage = "INCOMING NEW PLAYER...";
|
||||
currentMessage2 = "25¢ 25¢ 25¢ 25¢ 25¢";
|
||||
}
|
||||
else if (playingSince<6000 && lifes==4) {
|
||||
announcePlayer(player);
|
||||
}
|
||||
else {
|
||||
currentMessageClr2 = "#f66";
|
||||
if (lastStatement && t - lastStatementTime < 3000) {
|
||||
currentMessage2 = lastStatement;
|
||||
}
|
||||
else {
|
||||
if (neverPlayed) {
|
||||
if (helpVisible()) {
|
||||
currentMessageClr = currentMessageClr2 = "#f7c";
|
||||
currentMessage = MOBILE ? "TAP ON ASTEROIDS" : "PRESS ASTEROIDS LETTER";
|
||||
currentMessage2 = "TO SEND THEM TO THE GAME";
|
||||
}
|
||||
}
|
||||
else if (lifes > 4 && t - lastExtraLife > 5000) {
|
||||
currentMessageClr = currentMessageClr2 = "#f66";
|
||||
currentMessage = "DON'T LET PLAYER";
|
||||
currentMessage2 = "REACH ᐃᐃᐃᐃᐃ !!!";
|
||||
}
|
||||
else if (score > 10000 && t - lastExtraLife < 4500) {
|
||||
currentMessageClr = currentMessageClr2 = "#f66";
|
||||
currentMessage = "OH NO! PLAYER JUST";
|
||||
currentMessage2 = "WON AN EXTRA LIFE!";
|
||||
}
|
||||
else if (player==2 && 5000<playingSince) {
|
||||
currentMessageClr2 = currentMessageClr = "#7cf";
|
||||
currentMessage = "LETS TRAIN WITH...";
|
||||
currentMessage2 = "AIMING";
|
||||
}
|
||||
else if (player==3 && 5000<playingSince) {
|
||||
currentMessageClr = "#7cf";
|
||||
currentMessageClr2 = "#f66";
|
||||
currentMessage = "CAREFUL ABOUT THE";
|
||||
currentMessage2 = "RED AIMING";
|
||||
}
|
||||
else if (player==4 && 5000<playingSince && neverUFOs) {
|
||||
currentMessageClr = currentMessageClr2 = "#f7c";
|
||||
currentMessage = "MAKE COMBOS TO SEND";
|
||||
currentMessage2 = "AN UFO !!!";
|
||||
}
|
||||
else if (player > 5) {
|
||||
lastStatement = 0;
|
||||
if (Math.random() < 0.0001 * dt && t - lastStatementTime > 8000) {
|
||||
currentMessage2 = [
|
||||
"COME ON! KILL IT!",
|
||||
"JUST DO IT!",
|
||||
"I WANT ¢¢¢",
|
||||
"GIVE ME SOME ¢¢¢",
|
||||
"DO IT!",
|
||||
"DESTROY IT!"
|
||||
];
|
||||
lastStatement = currentMessage2 = currentMessage2[Math.floor(Math.random() * currentMessage2.length)];
|
||||
lastStatementTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMessage2 && lastMessage2 !== currentMessage2 &&
|
||||
(currentMessageClr2 == "#f66" || currentMessageClr2 == "#f7c")) {
|
||||
play(Amsg);
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(GAME_MARGIN, MOBILE ? 40 : 2);
|
||||
ctx.lineWidth = (t%600>300) ? 2 : 1;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = currentMessageClr;
|
||||
font(currentMessage, MOBILE ? 1.5 : 2, 1);
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
ctx.strokeStyle = currentMessageClr2;
|
||||
ctx.translate(0, MOBILE ? 30 : 40);
|
||||
font(lastMessage2 = currentMessage2, MOBILE ? 1.5 : 2, 1);
|
||||
ctx.restore();
|
||||
ctx.restore();
|
||||
|
||||
if (gameOver) return;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(FW - GAME_MARGIN, 2);
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "#7cf";
|
||||
font(((playingSince>0&&awaitingContinue||player)*25)+"¢", 2, -1);
|
||||
ctx.restore();
|
||||
|
||||
|
||||
ctx.save();
|
||||
ctx.globalAlpha = musicTick ? 1 : 0.6;
|
||||
ctx.strokeStyle = "#7cf";
|
||||
ctx.translate(FW - GAME_MARGIN, FH - 30);
|
||||
if (combos) font(combos+"x", 1.5, -1);
|
||||
ctx.restore();
|
||||
|
||||
/*
|
||||
if (combos && combosTarget-combos < 9) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = "#7cf";
|
||||
ctx.globalAlpha = musicTick ? 1 : 0.5;
|
||||
ctx.translate(FW - GAME_MARGIN, FH - 50);
|
||||
font((1+combosTarget-combos)+" ", 1, -1);
|
||||
ctx.translate(0, 0);
|
||||
path(UFO);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
*/
|
||||
|
||||
if (achievements) {
|
||||
ctx.save();
|
||||
ctx.translate(GAME_MARGIN + 50, FH - 20);
|
||||
ctx.strokeStyle = "#fc7";
|
||||
drawAchievements(1);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
function drawAchievements (fontSize) {
|
||||
for (var j = 0; j < 3; j++) {
|
||||
var badge = achievements[j];
|
||||
if (badge) {
|
||||
ctx.save();
|
||||
ctx.translate(100 * j, 0);
|
||||
path(badgesIcons[j]);
|
||||
ctx.stroke();
|
||||
ctx.translate(0, -20 - 10 * fontSize);
|
||||
font(""+badge, fontSize);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
3180
cookbook/src/examples/behindasteroids/build.js
Normal file
339
cookbook/src/examples/behindasteroids/index.js
Executable file
@ -0,0 +1,339 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Shaders, Node, GLSL, Bus, Backbuffer } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import gameBuild from "./build";
|
||||
|
||||
/**
|
||||
* This example reproduce the after effects made in a js13k game:
|
||||
* https://github.com/gre/behind-asteroids
|
||||
* see also https://github.com/gre/behind-asteroids/blob/master/src/effects.js
|
||||
*/
|
||||
|
||||
const shaders = Shaders.create({
|
||||
blur1d: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform vec2 dim;
|
||||
uniform vec2 dir;
|
||||
void main() {
|
||||
vec4 color = vec4(0.0);
|
||||
vec2 off1 = vec2(1.3846153846) * dir;
|
||||
vec2 off2 = vec2(3.2307692308) * dir;
|
||||
color += texture2D(t, uv) * 0.2270270270;
|
||||
color += texture2D(t, uv + (off1 / dim)) * 0.3162162162;
|
||||
color += texture2D(t, uv - (off1 / dim)) * 0.3162162162;
|
||||
color += texture2D(t, uv + (off2 / dim)) * 0.0702702703;
|
||||
color += texture2D(t, uv - (off2 / dim)) * 0.0702702703;
|
||||
gl_FragColor = color;
|
||||
}
|
||||
`
|
||||
},
|
||||
game: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D G;
|
||||
uniform sampler2D R;
|
||||
uniform sampler2D B;
|
||||
uniform sampler2D L;
|
||||
uniform sampler2D E;
|
||||
uniform float s;
|
||||
uniform float F;
|
||||
uniform vec2 k;
|
||||
float squircleDist (vec2 a, vec2 b) {
|
||||
float p = 10.0;
|
||||
vec2 c = a-b;
|
||||
return pow(abs(pow(abs(c.x), p)+pow(abs(c.y), p)), 1.0/p);
|
||||
}
|
||||
void main() {
|
||||
vec2 UV = uv + k;
|
||||
vec2 pos = (UV/0.98)-0.01;
|
||||
float d = squircleDist(UV, vec2(0.5));
|
||||
float dd = smoothstep(0.45, 0.51, d);
|
||||
pos = mix(pos, vec2(0.5), 0.2 * (0.6 - d) - 0.02 * d);
|
||||
vec3 gc = texture2D(G, pos).rgb;
|
||||
gl_FragColor =
|
||||
step(0.0, UV.x) *
|
||||
step(UV.x, 1.0) *
|
||||
step(0.0, UV.y) *
|
||||
step(UV.y, 1.0) *
|
||||
vec4((
|
||||
vec3(0.03 + 0.1 * F, 0.04, 0.05) +
|
||||
mix(vec3(0.05, 0.1, 0.15) - gc, 2.0 * gc, s) +
|
||||
s * (
|
||||
texture2D(L, pos).rgb +
|
||||
vec3(0.3 + F, 0.6, 1.0) * (
|
||||
texture2D(R, pos).rgb +
|
||||
3.0 * texture2D(B, pos).rgb
|
||||
) +
|
||||
0.5 * texture2D(E, pos).rgb
|
||||
)
|
||||
)
|
||||
* mix(1.0, smoothstep(1.0, 0.0, dd), 0.6), 1.0);
|
||||
}
|
||||
`
|
||||
},
|
||||
glare: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
void main() {
|
||||
gl_FragColor = vec4(step(0.9, texture2D(t, uv).r));
|
||||
}
|
||||
`
|
||||
},
|
||||
laser: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
void main() {
|
||||
vec3 c = texture2D(t, uv).rgb;
|
||||
vec2 off = 0.003 * vec2(
|
||||
cos(47.0 * uv.y),
|
||||
sin(67.0 * uv.x)
|
||||
);
|
||||
gl_FragColor = vec4(
|
||||
c.r + c.g + c.b + texture2D(t, uv+off).b
|
||||
);
|
||||
}
|
||||
|
||||
`
|
||||
},
|
||||
persistence: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform sampler2D r;
|
||||
void main() {
|
||||
vec3 b = texture2D(r, uv).rgb;
|
||||
gl_FragColor = vec4(
|
||||
b * (0.82 - 0.3 * b.r * b.r) +
|
||||
texture2D(t, uv).rgb,
|
||||
1.0);
|
||||
}
|
||||
`
|
||||
|
||||
},
|
||||
player: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform float pt;
|
||||
uniform float pl;
|
||||
uniform float S;
|
||||
uniform float ex;
|
||||
uniform float J;
|
||||
uniform float P;
|
||||
float disc (vec2 c, vec2 r) {
|
||||
return step(length((uv - c) / r), 1.0);
|
||||
}
|
||||
float squircle (vec2 c, vec2 r, float p) {
|
||||
vec2 v = (uv - c) / r;
|
||||
return step(pow(abs(v.x), p) + pow(abs(v.y), p), 1.0);
|
||||
}
|
||||
vec3 env () {
|
||||
return 0.1 +
|
||||
0.3 * vec3(1.0, 0.9, 0.7) *
|
||||
smoothstep(0.4, 0.1, distance(uv, vec2(0.2, 1.2))) +
|
||||
0.4 * vec3(0.8, 0.6, 1.0) *
|
||||
smoothstep(0.5, 0.2, distance(uv, vec2(1.3, 0.7)));
|
||||
}
|
||||
vec4 player (float p, float dx) {
|
||||
vec4 c = vec4(0.0);
|
||||
vec2 e = vec2(
|
||||
min(ex, 1.0),
|
||||
mix(min(ex, 1.0), min(ex-1.0, 1.0), 0.5));
|
||||
vec4 skin = 0.2 + 0.4 * pow(abs(cos(4.*p+S)), 2.0) *
|
||||
vec4(1.0, 0.7, 0.3, 1.0);
|
||||
vec4 hair = vec4(0.5, 0.3, 0.3, 1.0);
|
||||
vec4 sweater = vec4(
|
||||
0.3 * (1.0 + cos(3.*p + 6.*S)),
|
||||
0.2 * (1.0 + cos(7.*p + 7.*S)),
|
||||
0.1+0.2 * (1.0 + sin(7.*p + 8.*S)),
|
||||
1.0);
|
||||
float feminity = step(sin(9.0*p+S), 0.0);
|
||||
float hairSize = 0.02 + 0.02 * feminity * cos(p+S);
|
||||
float walk = step(dx, -0.01) + step(0.01, dx);
|
||||
float play = (1.0 - walk) * step(0.0, pt);
|
||||
vec2 pos = vec2(0.5) +
|
||||
J * vec2(0.0, 0.2) +
|
||||
walk * vec2(
|
||||
0.03 * cos(4.0*pt + sin(pt)),
|
||||
0.05 * abs(sin(3.0*pt))) +
|
||||
e * play * (1.0 - P) * vec2(
|
||||
0.05 * cos(pt * (1.0 + 0.1 * sin(pt))),
|
||||
0.05 * abs(sin(pt)));
|
||||
vec2 pos2 = mix(pos, vec2(0.5), 0.5);
|
||||
pos.x += dx;
|
||||
pos2.x += dx;
|
||||
c += skin * disc(pos, vec2(0.06, 0.1));
|
||||
c *= 1.0 - (0.5 + 0.5 * feminity) *
|
||||
disc(pos - vec2(0.0, 0.04), vec2(0.03, 0.01));
|
||||
c *= 1.0 - disc(pos + vec2(0.03, 0.03), vec2(0.02, 0.01));
|
||||
c *= 1.0 - disc(pos + vec2(-0.03, 0.03), vec2(0.02, 0.01));
|
||||
c *= 1.0 - 0.6 * disc(pos, vec2(0.01, 0.02));
|
||||
c += hair * disc(pos + vec2(0.0, hairSize), vec2(0.07, 0.1 + hairSize));
|
||||
c += play * (hair + skin) * disc(pos2 - vec2(
|
||||
-0.2 + 0.01 * cos(5.0*pt),
|
||||
0.45 - 0.1 * e.y * step(0.0, pt) * P * pow(abs(sin(8.0 * pt *
|
||||
(1.0 + 0.2 * cos(pt)))), 4.0)
|
||||
), vec2(0.055, 0.05));
|
||||
c += play * (hair + skin) * disc(pos2 - vec2(
|
||||
0.2 + 0.01 * cos(5.0*pt),
|
||||
0.45 - 0.1 * e.x * step(2.0, pt) * P * pow(abs(cos(7.0 * pt)), 4.0)
|
||||
), vec2(0.055, 0.05));
|
||||
c += step(c.a, 0.0) * (hair + skin) *
|
||||
squircle(pos - vec2(0.0, 0.10 + 0.02 * feminity),
|
||||
vec2(0.05 - 0.01 * feminity, 0.03), 4.0);
|
||||
vec2 sr = vec2(
|
||||
0.16 + 0.04 * sin(9.*p),
|
||||
0.27 + 0.02 * cos(9.*p));
|
||||
c += step(c.r+c.g+c.b, 0.0) * sweater * step(1.0,
|
||||
squircle(pos - vec2(0.0, 0.35), sr * (1.0 - 0.1 * feminity), 4.0) +
|
||||
disc(pos - vec2(0.0, 0.35), sr));
|
||||
return c;
|
||||
}
|
||||
void main() {
|
||||
float light = 0.6 + 0.4 * smoothstep(2.0, 0.0, distance(pt, -2.0));
|
||||
vec4 c = vec4(0.0);
|
||||
c += (1.0 - smoothstep(-0.0, -5.0, pt)) *
|
||||
player(pl+step(pt, 0.0), -0.6 * smoothstep(-1., -5., pt));
|
||||
c += step (1.0, pl) *
|
||||
player(pl+step(pt, 0.0)-1.0, 2.0 *smoothstep(-4., -1., pt));
|
||||
c *= 1.0 - 1.3 * distance(uv, vec2(0.5));
|
||||
gl_FragColor = vec4(light * mix(env(), c.rgb, clamp(c.a, 0.0, 1.0)), 1.0);
|
||||
}
|
||||
`
|
||||
}
|
||||
});
|
||||
|
||||
const Blur1D =
|
||||
({ dim, dir, children: t }) =>
|
||||
<Node shader={shaders.blur1d} uniforms={{ dim, dir, t }} />;
|
||||
|
||||
export default class Example extends Component {
|
||||
render () {
|
||||
const {showCanvas} = this.props;
|
||||
const {pt,pl,ex,J,P,s,F,k,S,W,H} =
|
||||
// HACK to just render the game
|
||||
this._ ? this._.getWebGLParams() :
|
||||
({ pt: 0, pl: 0, ex: 0, J: 0, P: 0, s: 0, F: 0, k: [0,0], W: 2, H: 2, S: 0 });
|
||||
const dim = [ W, H ];
|
||||
|
||||
return (
|
||||
<div style={{ background: "black" }} ref="container">
|
||||
|
||||
<Surface width={W} height={H} pixelRatio={1}>
|
||||
|
||||
<Bus ref="laser">
|
||||
<Node
|
||||
shader={shaders.laser}
|
||||
uniforms={{ t: () => this.refs.gameCanvas }}
|
||||
/>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="player">
|
||||
<Blur1D dim={dim} dir={[ 0, 2 ]}>
|
||||
<Blur1D dim={dim} dir={[ 6, 0 ]}>
|
||||
<Blur1D dim={dim} dir={[ 2, 2 ]}>
|
||||
<Blur1D dim={dim} dir={[ -2, 2 ]}>
|
||||
<Node
|
||||
shader={shaders.player}
|
||||
uniforms={{ pt, pl, ex, J, P, S }}
|
||||
/>
|
||||
</Blur1D>
|
||||
</Blur1D>
|
||||
</Blur1D>
|
||||
</Blur1D>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="glare">
|
||||
<Blur1D dim={dim} dir={[ 2, -4 ]}>
|
||||
<Node
|
||||
shader={shaders.glare}
|
||||
uniforms={{ t: () => this.refs.laser }}
|
||||
/>
|
||||
</Blur1D>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="glareCursor">
|
||||
<Blur1D dim={dim} dir={[ 4, -8 ]}>
|
||||
{() => this.refs.glare}
|
||||
</Blur1D>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="glareBlurred">
|
||||
<Blur1D dim={dim} dir={[ 0, 1 ]}>
|
||||
<Blur1D dim={dim} dir={[ 1, 0 ]}>
|
||||
<Blur1D dim={dim} dir={[ -0.5, 0.5 ]}>
|
||||
<Blur1D dim={dim} dir={[ 0.5, 0.5 ]}>
|
||||
{() => this.refs.laser
|
||||
//FIXME this should be glare instead.
|
||||
//but i think there is a bug in gl-react!
|
||||
}
|
||||
</Blur1D>
|
||||
</Blur1D>
|
||||
</Blur1D>
|
||||
</Blur1D>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="persistence">
|
||||
<Node
|
||||
shader={shaders.persistence}
|
||||
backbuffering
|
||||
uniforms={{
|
||||
t: this.refs.glareBlurred,
|
||||
r: Backbuffer
|
||||
}}
|
||||
/>
|
||||
</Bus>
|
||||
|
||||
<Node
|
||||
shader={shaders.game}
|
||||
uniforms={{
|
||||
G: () => this.refs.laser,
|
||||
R: () => this.refs.persistence,
|
||||
B: () => this.refs.glareBlurred,
|
||||
L: () => this.refs.glareCursor,
|
||||
E: () => this.refs.player,
|
||||
s,
|
||||
F,
|
||||
k
|
||||
}} />
|
||||
</Surface>
|
||||
|
||||
<canvas id="c" ref="gameCanvas" hidden={!showCanvas} />
|
||||
|
||||
<div style={{ textAlign: "center", padding: 20 }}>
|
||||
<button onClick={this.sendAsteroid}>
|
||||
SEND ASTEROID!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_: any;
|
||||
componentDidMount () {
|
||||
this._ = gameBuild(
|
||||
this.refs.container,
|
||||
this.refs.gameCanvas,
|
||||
() => this.forceUpdate()
|
||||
);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this._.dispose();
|
||||
}
|
||||
|
||||
sendAsteroid = () => window._behindAsteroids_send();
|
||||
|
||||
static defaultProps = {
|
||||
showCanvas: false,
|
||||
};
|
||||
}
|
||||
18
cookbook/src/examples/behindasteroids/meta.js
Executable file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
export const title = "Behind Asteroids (js13k 2015 – greweb)";
|
||||
|
||||
export const toolbox = [
|
||||
{ prop: "showCanvas",
|
||||
title: "Under the hood",
|
||||
Editor:
|
||||
({ value, onChange }) =>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" checked={value} onChange={e => onChange(e.target.checked)} />
|
||||
Show the 2D Canvas rendered by the game
|
||||
</label>
|
||||
<p><em>The rest of the rendering is done in WebGL (ported in gl-react).</em></p>
|
||||
</div>
|
||||
},
|
||||
];
|
||||
165
cookbook/src/examples/blurimgtitle/index.js
Executable file
@ -0,0 +1,165 @@
|
||||
//@flow
|
||||
import React, { PureComponent, Component, PropTypes } from "react";
|
||||
import { Shaders, Node, GLSL, Bus, LinearCopy } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import JSON2D from "react-json2d";
|
||||
import {Blur1D} from "../blurxy";
|
||||
import {Blur} from "../blurmulti";
|
||||
import {BlurV} from "../blurmap";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
ImageTitle: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D img, imgBlurred, imgTone, title, blurMap;
|
||||
uniform float colorThreshold;
|
||||
float monochrome (vec3 c) {
|
||||
return 0.2125 * c.r + 0.7154 * c.g + 0.0721 * c.b;
|
||||
}
|
||||
void main() {
|
||||
float blurFactor = texture2D(blurMap, uv).r;
|
||||
vec4 bgColor = mix(
|
||||
texture2D(img, uv),
|
||||
texture2D(imgBlurred, uv),
|
||||
step(0.01, blurFactor)
|
||||
);
|
||||
vec4 textColor = vec4(vec3(
|
||||
step(monochrome(texture2D(imgTone, uv).rgb), colorThreshold)
|
||||
), 1.0);
|
||||
float isText = 1.0 - texture2D(title, uv).r;
|
||||
gl_FragColor = mix(bgColor, textColor, isText);
|
||||
}`
|
||||
},
|
||||
TitleBlurMap: {
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform float threshold;
|
||||
void main() {
|
||||
gl_FragColor = vec4(
|
||||
vec3(smoothstep(1.0, threshold, texture2D(t, uv).r)),
|
||||
1.0);
|
||||
}`
|
||||
},
|
||||
});
|
||||
|
||||
const AveragePixels = ({ children, quality }) =>
|
||||
<Blur1D
|
||||
width={1}
|
||||
height={1}
|
||||
resolution={[ 1, 1 ]}
|
||||
direction={[ 0, 0.1 ]}>
|
||||
<Blur1D
|
||||
width={1}
|
||||
height={quality}
|
||||
resolution={[ 1, quality ]}
|
||||
direction={[ 0.1, 0 ]}>
|
||||
{children}
|
||||
</Blur1D>
|
||||
</Blur1D>;
|
||||
|
||||
const TitleBlurMap = ({ children: title, threshold }) =>
|
||||
<Node
|
||||
shader={shaders.TitleBlurMap}
|
||||
uniforms={{
|
||||
threshold,
|
||||
t:
|
||||
<Blur factor={4} passes={4} width={200} height={200}>
|
||||
{title}
|
||||
</Blur>
|
||||
}}
|
||||
width={64}
|
||||
height={64}
|
||||
/>;
|
||||
|
||||
class Title extends PureComponent {
|
||||
render () {
|
||||
const { children, width, height } = this.props;
|
||||
return <LinearCopy><JSON2D width={width} height={height}>{{
|
||||
size: [ width, height ],
|
||||
background: "#fff",
|
||||
draws: [
|
||||
{
|
||||
"font": "bold 78px Didot,Georgia,serif",
|
||||
"fillStyle": "#000",
|
||||
"textBaseline": "top",
|
||||
"textAlign": "center"
|
||||
},
|
||||
[ "fillText", children, width/2, 10, 84 ],
|
||||
]
|
||||
}}</JSON2D></LinearCopy>;
|
||||
}
|
||||
}
|
||||
|
||||
class ImageTitle extends Component {
|
||||
static contextTypes = {
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
};
|
||||
render() {
|
||||
const { children: img, text, colorThreshold } = this.props;
|
||||
const { width, height } = this.context;
|
||||
return (
|
||||
<Node
|
||||
shader={shaders.ImageTitle}
|
||||
uniforms={{
|
||||
colorThreshold,
|
||||
img,
|
||||
imgBlurred:() => this.refs.imgBlurred,
|
||||
title:() => this.refs.title,
|
||||
imgTone:() => this.refs.imgTone,
|
||||
blurMap:() => this.refs.blurMap,
|
||||
}}>
|
||||
|
||||
<Bus ref="title">
|
||||
<Title width={width} height={height}>
|
||||
{text}
|
||||
</Title>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="blurMap">
|
||||
<TitleBlurMap threshold={0.7}>
|
||||
{() => this.refs.title}
|
||||
</TitleBlurMap>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="imgTone">
|
||||
<AveragePixels quality={8}>
|
||||
{img}
|
||||
</AveragePixels>
|
||||
</Bus>
|
||||
|
||||
<Bus ref="imgBlurred">
|
||||
<BlurV
|
||||
map={() => this.refs.blurMap}
|
||||
factor={4}
|
||||
passes={4}>
|
||||
{img}
|
||||
</BlurV>
|
||||
</Bus>
|
||||
|
||||
</Node>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Example extends Component {
|
||||
render() {
|
||||
const { title, colorThreshold, image } = this.props;
|
||||
return (
|
||||
<Surface width={450} height={300}>
|
||||
<ImageTitle text={title} colorThreshold={colorThreshold}>
|
||||
{image}
|
||||
</ImageTitle>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
title: "Hello\nSan Francisco\n☻",
|
||||
colorThreshold: 0.6,
|
||||
image: require("./sf-6.jpg"),
|
||||
};
|
||||
}
|
||||
36
cookbook/src/examples/blurimgtitle/meta.js
Executable file
@ -0,0 +1,36 @@
|
||||
import ImagesPicker from "../../toolbox/ImagesPicker";
|
||||
import makeTextArea from "../../toolbox/makeTextArea";
|
||||
import makeFloatSlider from "../../toolbox/makeFloatSlider";
|
||||
|
||||
export const title = "Dynamic Blur Image Title";
|
||||
|
||||
export const toolbox = [
|
||||
{ prop: "title",
|
||||
title: "Title",
|
||||
Editor: makeTextArea({
|
||||
height: 140,
|
||||
padding: 6,
|
||||
fontFamily: "Didot,Georgia,serif",
|
||||
fontSize: "36px",
|
||||
lineHeight: "42px",
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
}) },
|
||||
{ prop: "colorThreshold",
|
||||
title: "Color Threshold",
|
||||
Editor: makeFloatSlider(0, 1, 0.01) }, // FIXME black <-> white
|
||||
{ prop: "image",
|
||||
title: "Image",
|
||||
Editor: ImagesPicker,
|
||||
style: { width: 400 },
|
||||
imageStyle: { maxWidth: 56, maxHeight: 56, marginBottom: 16, },
|
||||
images: [
|
||||
require("./sf-1.jpg"),
|
||||
require("./sf-2.jpg"),
|
||||
require("./sf-3.jpg"),
|
||||
require("./sf-4.jpg"),
|
||||
require("./sf-5.jpg"),
|
||||
require("./sf-6.jpg"),
|
||||
require("./sf-7.jpg"),
|
||||
] },
|
||||
];
|
||||
BIN
cookbook/src/examples/blurimgtitle/sf-1.jpg
Executable file
|
After Width: | Height: | Size: 169 KiB |
BIN
cookbook/src/examples/blurimgtitle/sf-2.jpg
Executable file
|
After Width: | Height: | Size: 259 KiB |
BIN
cookbook/src/examples/blurimgtitle/sf-3.jpg
Executable file
|
After Width: | Height: | Size: 112 KiB |
BIN
cookbook/src/examples/blurimgtitle/sf-4.jpg
Executable file
|
After Width: | Height: | Size: 138 KiB |
BIN
cookbook/src/examples/blurimgtitle/sf-5.jpg
Executable file
|
After Width: | Height: | Size: 220 KiB |
BIN
cookbook/src/examples/blurimgtitle/sf-6.jpg
Executable file
|
After Width: | Height: | Size: 401 KiB |
BIN
cookbook/src/examples/blurimgtitle/sf-7.jpg
Executable file
|
After Width: | Height: | Size: 168 KiB |
68
cookbook/src/examples/blurmap/index.js
Executable file
@ -0,0 +1,68 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Shaders, Node, GLSL, connectSize } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
blurV1D: {
|
||||
frag: GLSL`precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t, map;
|
||||
uniform vec2 direction, resolution;
|
||||
vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
|
||||
vec4 color = vec4(0.0);
|
||||
vec2 off1 = vec2(1.3846153846) * direction;
|
||||
vec2 off2 = vec2(3.2307692308) * direction;
|
||||
color += texture2D(image, uv) * 0.2270270270;
|
||||
color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162;
|
||||
color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162;
|
||||
color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703;
|
||||
color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703;
|
||||
return color;
|
||||
}
|
||||
void main() {
|
||||
gl_FragColor = blur9(t, uv, resolution, direction * texture2D(map, uv).rg);
|
||||
}`
|
||||
}
|
||||
});
|
||||
|
||||
// Same concept than Blur1D except it takes one more prop:
|
||||
// a map texture that tells the blur intensity for a given position.
|
||||
export const BlurV1D =
|
||||
connectSize(({ children: t, direction, map, width, height }) =>
|
||||
<Node
|
||||
shader={shaders.blurV1D}
|
||||
uniforms={{ t, map, resolution: [ width, height ], direction }}
|
||||
/>);
|
||||
|
||||
// And its N-pass version
|
||||
import {directionForPass} from "../blurmulti";
|
||||
export const BlurV =
|
||||
connectSize(({ children, factor, map, passes }) => {
|
||||
const rec = pass =>
|
||||
pass <= 0
|
||||
? children
|
||||
: <BlurV1D map={map} direction={directionForPass(pass, factor, passes)}>
|
||||
{rec(pass-1)}
|
||||
</BlurV1D>;
|
||||
return rec(passes);
|
||||
});
|
||||
|
||||
export default class Example extends Component {
|
||||
render() {
|
||||
const { factor, passes, map } = this.props;
|
||||
return (
|
||||
<Surface width={600} height={284}>
|
||||
<BlurV map={map} passes={passes} factor={factor}>
|
||||
https://i.imgur.com/NjbLHx2.jpg
|
||||
</BlurV>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
static defaultProps = {
|
||||
factor: 2,
|
||||
passes: 4,
|
||||
map: StaticBlurMap.images[0],
|
||||
};
|
||||
};
|
||||
import StaticBlurMap from "../../toolbox/StaticBlurMap";
|
||||
21
cookbook/src/examples/blurmap/meta.js
Executable file
@ -0,0 +1,21 @@
|
||||
import markdown from "../../markdown";
|
||||
import StaticBlurMap from "../../toolbox/StaticBlurMap";
|
||||
import makeFloatSlider from "../../toolbox/makeFloatSlider";
|
||||
export const title = "Blur with intensity map & multi-pass";
|
||||
export const desc = markdown`
|
||||
common use-case: vary Blur based on the position
|
||||
`;
|
||||
export const descAfter = markdown`
|
||||
We use a texture to map the depth of the Blur.
|
||||
`;
|
||||
export const toolbox = [
|
||||
{ prop: "factor",
|
||||
title: "Blur",
|
||||
Editor: makeFloatSlider(0, 8, 0.2) },
|
||||
{ prop: "passes",
|
||||
title: value => `Blur Passes (${value})`,
|
||||
Editor: makeFloatSlider(1, 8, 1) },
|
||||
{ prop: "map",
|
||||
title: "Blur Texture Map",
|
||||
Editor: StaticBlurMap },
|
||||
];
|
||||
46
cookbook/src/examples/blurmapdyn/index.js
Executable file
@ -0,0 +1,46 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Shaders, Node, Bus, GLSL } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import {BlurV} from "../blurmap";
|
||||
import timeLoop from "../../HOC/timeLoop";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
ConicalGradiant: {
|
||||
frag: GLSL`precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform float phase;
|
||||
const float PI = 3.14159;
|
||||
void main () {
|
||||
gl_FragColor = vec4(vec3(
|
||||
mod(phase + atan(uv.x-0.5, uv.y-0.5)/(2.0*PI), 1.0)
|
||||
), 1.0);
|
||||
}` }
|
||||
});
|
||||
|
||||
const ConicalGradiantLoop = timeLoop(({ time }) =>
|
||||
<Node
|
||||
shader={shaders.ConicalGradiant}
|
||||
uniforms={{ phase: time/3000 }}
|
||||
/>);
|
||||
|
||||
export default class Example extends Component {
|
||||
render() {
|
||||
const { factor, passes } = this.props;
|
||||
// <ConicalGradiant/> also needs to be computed once.
|
||||
return (
|
||||
<Surface width={600} height={284}>
|
||||
<Bus ref="blurMapBus">
|
||||
<ConicalGradiantLoop />
|
||||
</Bus>
|
||||
<BlurV map={() => this.refs.blurMapBus} passes={passes} factor={factor}>
|
||||
https://i.imgur.com/NjbLHx2.jpg
|
||||
</BlurV>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
static defaultProps = {
|
||||
factor: 6,
|
||||
passes: 4,
|
||||
};
|
||||
};
|
||||
14
cookbook/src/examples/blurmapdyn/meta.js
Executable file
@ -0,0 +1,14 @@
|
||||
import markdown from "../../markdown";
|
||||
import makeFloatSlider from "../../toolbox/makeFloatSlider";
|
||||
export const title = "Blur with intensity map & multi-pass";
|
||||
export const desc = markdown`
|
||||
Any arbitrary shader can be used as a blur map!
|
||||
`;
|
||||
export const toolbox = [
|
||||
{ prop: "factor",
|
||||
title: "Blur",
|
||||
Editor: makeFloatSlider(0, 8, 0.2) },
|
||||
{ prop: "passes",
|
||||
title: value => `Blur Passes (${value})`,
|
||||
Editor: makeFloatSlider(1, 8, 1) },
|
||||
];
|
||||
62
cookbook/src/examples/blurmapmouse/index.js
Executable file
@ -0,0 +1,62 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Shaders, Node, Bus, GLSL } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import {BlurV} from "../blurmap";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
Offset: {
|
||||
frag: GLSL`precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform vec2 offset;
|
||||
void main () {
|
||||
gl_FragColor = texture2D(t, uv + offset);
|
||||
}`
|
||||
}
|
||||
});
|
||||
|
||||
const Offset = ({ t, offset }) =>
|
||||
<Node shader={shaders.Offset} uniforms={{ t, offset }} />;
|
||||
|
||||
export default class Example extends Component {
|
||||
state = {
|
||||
offset: [0,0],
|
||||
};
|
||||
render() {
|
||||
const { map } = this.props;
|
||||
const { offset } = this.state;
|
||||
// Sharing computation of a GL Node.
|
||||
// <Offset /> should not be passed straight to BlurV's map because
|
||||
// it would duplicates it in the tree ([passes] times)
|
||||
// Instead, we need to express a graph and share the
|
||||
// computation with a Bus ref.
|
||||
// We pass to BlurV's map prop a function that resolve that ref.
|
||||
return (
|
||||
<Surface
|
||||
width={600}
|
||||
height={284}
|
||||
onMouseMove={this.onMouseMove}
|
||||
onMouseLeave={this.onMouseLeave}>
|
||||
<Bus ref="blurMapBus">
|
||||
<Offset offset={offset} t={map} />
|
||||
</Bus>
|
||||
<BlurV map={()=>this.refs.blurMapBus} passes={6} factor={6}>
|
||||
https://i.imgur.com/NjbLHx2.jpg
|
||||
</BlurV>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
onMouseMove = (e: *) => {
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
this.setState({ offset: [
|
||||
-(e.clientX - rect.left - rect.width/2) / rect.width,
|
||||
(e.clientY - rect.top - rect.height/2) / rect.height
|
||||
] });
|
||||
};
|
||||
onMouseLeave = () => this.setState({ offset: [0,0] });
|
||||
static defaultProps = {
|
||||
map: StaticBlurMap.images[0],
|
||||
};
|
||||
};
|
||||
import StaticBlurMap from "../../toolbox/StaticBlurMap";
|
||||
16
cookbook/src/examples/blurmapmouse/meta.js
Executable file
@ -0,0 +1,16 @@
|
||||
import markdown from "../../markdown";
|
||||
import StaticBlurMap from "../../toolbox/StaticBlurMap";
|
||||
export const title = "Blur map and Mouse position";
|
||||
export const desc = markdown`
|
||||
Dynamically change Blur Map with mouse move
|
||||
`;
|
||||
export const descAfter = markdown`
|
||||
This example is the first to show the need of Sharing Computation:
|
||||
We want the "offset" framebuffer to be computed once,
|
||||
we use gl-react [\`<Bus>\`](/api#bus) concept for this.
|
||||
`;
|
||||
export const toolbox = [
|
||||
{ prop: "map",
|
||||
title: "Blur Texture Map",
|
||||
Editor: StaticBlurMap },
|
||||
];
|
||||
45
cookbook/src/examples/blurmulti/index.js
Executable file
@ -0,0 +1,45 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { connectSize } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import { Blur1D } from "../blurxy";
|
||||
|
||||
// empirical strategy to chose a 2d vector for a blur pass
|
||||
const NORM = Math.sqrt(2)/2;
|
||||
export const directionForPass = (p: number, factor: number, total: number) => {
|
||||
const f = factor * 2 * Math.ceil(p / 2) / total;
|
||||
switch ((p-1) % 4) { // alternate horizontal, vertical and 2 diagonals
|
||||
case 0: return [f,0];
|
||||
case 1: return [0,f];
|
||||
case 2: return [f*NORM,f*NORM];
|
||||
default: return [f*NORM,-f*NORM];
|
||||
}
|
||||
}
|
||||
|
||||
// recursively apply Blur1D to make a multi pass Blur component
|
||||
export const Blur = connectSize(({ children, factor, passes }) => {
|
||||
const rec = pass =>
|
||||
pass <= 0
|
||||
? children
|
||||
: <Blur1D direction={directionForPass(pass, factor, passes)}>
|
||||
{rec(pass-1)}
|
||||
</Blur1D>;
|
||||
return rec(passes);
|
||||
});
|
||||
|
||||
export default class Example extends Component {
|
||||
render() {
|
||||
const { factor, passes } = this.props;
|
||||
return (
|
||||
<Surface width={400} height={300}>
|
||||
<Blur passes={passes} factor={factor}>
|
||||
https://i.imgur.com/iPKTONG.jpg
|
||||
</Blur>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
static defaultProps = {
|
||||
factor: 2,
|
||||
passes: 4,
|
||||
};
|
||||
}
|
||||
22
cookbook/src/examples/blurmulti/meta.js
Executable file
@ -0,0 +1,22 @@
|
||||
import markdown from "../../markdown";
|
||||
import makeFloatSlider from "../../toolbox/makeFloatSlider";
|
||||
export const title = "multi-pass Blur";
|
||||
export const desc = markdown`
|
||||
For a better Blur quality, we need more than 2 passes.
|
||||
We generalize the concept to make a N-passes component.
|
||||
4-passes Blur is good enough, even without downscaling.
|
||||
`;
|
||||
export const descAfter = markdown`
|
||||
We apply blur on various direction, not just X and Y axis.
|
||||
We also vary the intensity for each pass.
|
||||
\`directionForPass\` implements this empirical approach.
|
||||
`;
|
||||
|
||||
export const toolbox = [
|
||||
{ prop: "factor",
|
||||
title: "Blur",
|
||||
Editor: makeFloatSlider(0, 8, 0.2) },
|
||||
{ prop: "passes",
|
||||
title: passes => `Blur Passes (${passes})`,
|
||||
Editor: makeFloatSlider(0, 8, 1) },
|
||||
];
|
||||
45
cookbook/src/examples/blurvideo/index.js
Executable file
@ -0,0 +1,45 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import { Bus } from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
import {BlurV} from "../blurmap";
|
||||
import {Saturate} from "../saturation";
|
||||
import {Video, videoMP4} from "../video";
|
||||
|
||||
// We must use a <Bus> if we don't want the <video> element to be duplicated
|
||||
// per Blur pass.. Also since we can dynamically change the number of passes,
|
||||
// it changes the tree level, (e.g. Blur1D>Blur1D>video becomes Blur1D>video)
|
||||
// and React always destroys and recreates the instance during reconcialition.
|
||||
|
||||
export default class Example extends Component {
|
||||
render() {
|
||||
const { factor, passes, contrast, saturation, brightness, map } = this.props;
|
||||
return (
|
||||
<Surface width={480} height={360} pixelRatio={1}>
|
||||
<Bus ref="vid">
|
||||
<Saturate contrast={contrast} saturation={saturation} brightness={brightness}>
|
||||
{ redraw =>
|
||||
<Video onFrame={redraw} autoPlay loop>
|
||||
<source type="video/mp4" src={videoMP4} />
|
||||
</Video> }
|
||||
</Saturate>
|
||||
</Bus>
|
||||
<BlurV map={map} passes={passes} factor={factor}>
|
||||
{ // as a texture, we give a function that resolve the video ref
|
||||
() => this.refs.vid
|
||||
}
|
||||
</BlurV>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
contrast: 1,
|
||||
saturation: 1,
|
||||
brightness: 1,
|
||||
factor: 2,
|
||||
passes: 4,
|
||||
map: StaticBlurMap.images[0],
|
||||
};
|
||||
}
|
||||
import StaticBlurMap from "../../toolbox/StaticBlurMap";
|
||||
24
cookbook/src/examples/blurvideo/meta.js
Executable file
@ -0,0 +1,24 @@
|
||||
import makeFloatSlider from "../../toolbox/makeFloatSlider";
|
||||
import StaticBlurMap from "../../toolbox/StaticBlurMap";
|
||||
|
||||
export const toolbox = [
|
||||
{ prop: "contrast",
|
||||
title: "Contrast",
|
||||
Editor: makeFloatSlider(0, 2, 0.05) },
|
||||
{ prop: "saturation",
|
||||
title: "Saturation",
|
||||
Editor: makeFloatSlider(0, 2, 0.05) },
|
||||
{ prop: "brightness",
|
||||
title: "Brightness",
|
||||
Editor: makeFloatSlider(0, 2, 0.05) },
|
||||
{ prop: "factor",
|
||||
title: "Blur",
|
||||
Editor: makeFloatSlider(0, 8, 0.2) },
|
||||
{ prop: "passes",
|
||||
title: value => `Blur Passes (${value})`,
|
||||
Editor: makeFloatSlider(1, 8, 1) },
|
||||
{ prop: "map",
|
||||
title: "Blur Texture Map",
|
||||
Editor: StaticBlurMap },
|
||||
];
|
||||
export const title = "Video + multi-pass Blur + contrast/saturation/brightness";
|
||||
61
cookbook/src/examples/blurxy/index.js
Executable file
@ -0,0 +1,61 @@
|
||||
//@flow
|
||||
import React, { Component } from "react";
|
||||
import {Shaders, Node, GLSL, connectSize} from "gl-react";
|
||||
import { Surface } from "gl-react-dom";
|
||||
|
||||
const shaders = Shaders.create({
|
||||
blur1D: { // blur9: from https://github.com/Jam3/glsl-fast-gaussian-blur
|
||||
frag: GLSL`
|
||||
precision highp float;
|
||||
varying vec2 uv;
|
||||
uniform sampler2D t;
|
||||
uniform vec2 direction, resolution;
|
||||
vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
|
||||
vec4 color = vec4(0.0);
|
||||
vec2 off1 = vec2(1.3846153846) * direction;
|
||||
vec2 off2 = vec2(3.2307692308) * direction;
|
||||
color += texture2D(image, uv) * 0.2270270270;
|
||||
color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162;
|
||||
color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162;
|
||||
color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703;
|
||||
color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703;
|
||||
return color;
|
||||
}
|
||||
void main() {
|
||||
gl_FragColor = blur9(t, uv, resolution, direction);
|
||||
}` }
|
||||
});
|
||||
|
||||
// This implements a blur on a single direction (x or y axis for instance)
|
||||
// connectSize will inject for us the width/height from context if not provided
|
||||
export const Blur1D =
|
||||
connectSize(({ children: t, direction, width, height }) =>
|
||||
<Node
|
||||
shader={shaders.blur1D}
|
||||
uniforms={{ t, resolution: [ width, height ], direction }}
|
||||
/>);
|
||||
|
||||
// BlurXY is a basic blur that apply Blur1D on Y and then on X
|
||||
export const BlurXY =
|
||||
connectSize(({ factor, children }) =>
|
||||
<Blur1D direction={[ factor, 0 ]}>
|
||||
<Blur1D direction={[ 0, factor ]}>
|
||||
{children}
|
||||
</Blur1D>
|
||||
</Blur1D>);
|
||||
|
||||
export default class Example extends Component {
|
||||
render() {
|
||||
const { factor } = this.props;
|
||||
return (
|
||||
<Surface width={400} height={300}>
|
||||
<BlurXY factor={factor}>
|
||||
https://i.imgur.com/iPKTONG.jpg
|
||||
</BlurXY>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
static defaultProps = {
|
||||
factor: 1,
|
||||
};
|
||||
}
|
||||
22
cookbook/src/examples/blurxy/meta.js
Executable file
@ -0,0 +1,22 @@
|
||||
import markdown from "../../markdown";
|
||||
import makeFloatSlider from "../../toolbox/makeFloatSlider";
|
||||
|
||||
export const title = "simple Blur (2-passes)";
|
||||
|
||||
export const desc = markdown`
|
||||
Implementing Blur efficiently isn't trivial.
|
||||
This approach do 2 passes *(X and Y)*
|
||||
`;
|
||||
export const descAfter = markdown`
|
||||
We apply a linear blur (9 gaussian lookup)
|
||||
on Y axis and then do the same on X axis.
|
||||
|
||||
We can see the blur quality isn't perfect,
|
||||
[next example](/blurxydownscale) will improve that.
|
||||
`;
|
||||
|
||||
export const toolbox = [
|
||||
{ prop: "factor",
|
||||
title: "Blur",
|
||||
Editor: makeFloatSlider(0, 8, 0.2) },
|
||||
];
|
||||