src/renderer/GPURenderer/Mobile/index.js
import { ParticleBuffer, Target, TextureAtlas, UniqueList } from '../common';
import { fragmentShader, vertexShader } from './shaders';
import BaseRenderer from '../../BaseRenderer';
import { DEFAULT_RENDERER_OPTIONS } from '../common/constants';
import { Pool } from '../../../core';
import { RENDERER_TYPE_GPU_MOBILE } from '../../types';
let THREE;
/**
* GPURenderer for mobile devices that do not support floating point textures.
*
* @author thrax <manthrax@gmail.com>
* @author rohan-deshpande <rohan@creativelifeform.com>
*/
export default class MobileGPURenderer extends BaseRenderer {
constructor(container, three, options = DEFAULT_RENDERER_OPTIONS) {
super(RENDERER_TYPE_GPU_MOBILE);
THREE = this.three = three;
const props = { ...DEFAULT_RENDERER_OPTIONS, ...options };
const {
camera,
maxParticles,
baseColor,
blending,
depthTest,
depthWrite,
transparent,
shouldDebugTextureAtlas,
} = props;
const particleBuffer = new ParticleBuffer(maxParticles, THREE);
const material = new THREE.ShaderMaterial({
uniforms: {
baseColor: { value: new THREE.Color(baseColor) },
uTexture: { value: null },
FFatlasIndex: { value: null },
atlasDim: { value: new THREE.Vector2() },
},
vertexShader: vertexShader(),
fragmentShader: fragmentShader(),
blending: THREE[blending],
depthTest,
depthWrite,
transparent,
});
this.camera = camera;
this.targetPool = new Pool();
this.uniqueList = new UniqueList(maxParticles);
this.particleBuffer = particleBuffer;
this.buffer = particleBuffer.buffer;
this.stride = particleBuffer.stride;
this.geometry = particleBuffer.geometry;
this.material = material;
this.points = new THREE.Points(this.geometry, this.material);
this.points.frustumCulled = false;
this.shouldDebugTextureAtlas = shouldDebugTextureAtlas;
container.add(this.points);
}
onSystemUpdate(system) {
super.onSystemUpdate(system);
this.buffer.needsUpdate = true;
const { textureAtlas } = this;
if (textureAtlas) {
textureAtlas.update();
this.material.uniforms.atlasDim.value.set(
textureAtlas.atlasTexture.image.width,
textureAtlas.atlasTexture.image.height
);
}
}
/**
* Pools the particle target if it does not exist.
* Updates the target and maps particle properties to the point.
*
* @param {Particle}
*/
onParticleCreated(particle) {
if (!particle.target) {
particle.target = this.targetPool.get(Target, THREE);
this.uniqueList.add(particle.id);
}
this.updateTarget(particle).mapParticleTargetPropsToPoint(particle);
}
/**
* Maps particle properties to the point if the particle has a target.
*
* @param {Particle}
*/
onParticleUpdate(particle) {
if (!particle.target) {
return;
}
this.updateTarget(particle).mapParticleTargetPropsToPoint(particle);
}
/**
* Resets and clears the particle target.
*
* @param {Particle}
*/
onParticleDead(particle) {
if (!particle.target) {
return;
}
particle.target.reset();
this.mapParticleTargetPropsToPoint(particle);
particle.target = null;
}
/**
* Maps all mutable properties from the particle to the target.
*
* @param {Particle}
* @return {GPURenderer}
*/
updateTarget(particle) {
const { position, rotation, scale, radius, color, alpha, body, id } = particle;
const { r, g, b } = color;
particle.target.position.copy(position);
particle.target.rotation.copy(rotation);
particle.target.size = scale * radius;
particle.target.color.setRGB(r, g, b);
particle.target.alpha = alpha;
particle.target.index = this.uniqueList.find(id);
if (body && body instanceof THREE.Sprite) {
const { map } = body.material;
particle.target.texture = map;
particle.target.textureIndex = this.getTextureID(
map,
this.shouldDebugTextureAtlas
);
}
return this;
}
/**
* Entry point for mapping particle properties to buffer geometry points.
*
* @param {Particle} particle - The particle containing the properties to map
* @return {GPURenderer}
*/
mapParticleTargetPropsToPoint(particle) {
this.updatePointPosition(particle)
.updatePointSize(particle)
.updatePointRotation(particle)
.updatePointColor(particle)
.updatePointAlpha(particle)
.updatePointTextureIndex(particle);
return this;
}
/**
* Updates the point's position according to the particle's target position.
*
* @param {Particle} particle - The particle containing the target position.
* @return {GPURenderer}
*/
updatePointPosition(particle) {
const attribute = 'position';
const { geometry, stride, buffer } = this;
const { target } = particle;
const { offset } = geometry.attributes[attribute];
buffer.array[target.index * stride + offset + 0] = target.position.x;
buffer.array[target.index * stride + offset + 1] = target.position.y;
buffer.array[target.index * stride + offset + 2] = target.position.z;
return this;
}
/**
* Updates the point's size relative to the particle's target scale and radius.
*
* @param {Particle} particle - The particle containing the target scale.
* @return {GPURenderer}
*/
updatePointSize(particle) {
const attribute = 'size';
const { geometry, stride, buffer } = this;
const { target } = particle;
const { offset } = geometry.attributes[attribute];
buffer.array[target.index * stride + offset + 0] = target.size;
return this;
}
/**
* Updates the point's rotation.
*
* @param {Particle} particle - The particle containing the target rotation.
* @return {GPURenderer}
*/
updatePointRotation(particle) {
const attribute = 'rotation';
const { geometry, stride, buffer } = this;
const { target } = particle;
const { offset } = geometry.attributes[attribute];
buffer.array[target.index * stride + offset + 0] = target.rotation.z;
return this;
}
/**
* Updates the point's color attribute according with the particle's target color.
*
* @param {Particle} particle - The particle containing the target color and alpha.
* @return {GPURenderer}
*/
updatePointColor(particle) {
const attribute = 'color';
const { geometry, stride, buffer } = this;
const { target } = particle;
const { offset } = geometry.attributes[attribute];
buffer.array[target.index * stride + offset + 0] = target.color.r;
buffer.array[target.index * stride + offset + 1] = target.color.g;
buffer.array[target.index * stride + offset + 2] = target.color.b;
return this;
}
/**
* Updates the point alpha attribute with the particle's target alpha.
*
* @param {Particle} particle - The particle containing the target alpha.
* @return {GPURenderer}
*/
updatePointAlpha(particle) {
const attribute = 'alpha';
const { geometry, stride, buffer } = this;
const { target } = particle;
const { offset } = geometry.attributes[attribute];
buffer.array[target.index * stride + offset + 0] = target.alpha;
return this;
}
/**
* Updates the point texture attribute with the particle's target texture.
*
* @param {Particle} particle - The particle containing the target texture.
* @return {GPURenderer}
*/
updatePointTextureIndex(particle) {
const attribute = 'texID';
const { geometry, stride, buffer } = this;
const { target } = particle;
const { offset } = geometry.attributes[attribute];
const id = target.index * stride + offset + 0;
// eslint-disable-next-line
if (false) {
buffer.array[id] = target.textureIndex;
} else {
let ti = target.textureIndex * 4;
const ta = this.textureAtlas;
const ida = ta.indexData;
const nx = ida[ti++];
const ny = ida[ti++];
const px = ida[ti++];
const py = ida[ti++];
buffer.array[id] = ((nx * ta.atlasTexture.image.width) | 0) + px;
buffer.array[id + 1] = ((ny * ta.atlasTexture.image.height) | 0) + py;
}
return this;
}
getTextureID(texture, debug) {
if (texture.textureIndex === undefined) {
if (!this.textureAtlas) {
this.textureAtlas = new TextureAtlas(this, debug);
}
this.textureAtlas.addTexture(texture);
}
return texture.textureIndex;
}
destroy() {
const { container, points, textureAtlas, uniqueList } = this;
container.remove(points);
uniqueList.destroy();
textureAtlas && textureAtlas.destroy();
}
}