import * as RE from 'rogue-engine' import * as THREE from 'three' import { Color } from 'three'; import Player from './Player.re'; //https://www.youtube.com/watch?v=OFqENgtqRAY export default class ParticleSystem extends RE.Component { vertexShader = ` uniform float pointMultiplier; varying vec4 vColor; void main() { vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_Position = projectionMatrix * mvPosition; gl_PointSize = pointMultiplier / gl_Position.w; vColor = color; }`; fragmentShader = ` uniform sampler2D diffuseTexture; varying vec4 vColor; void main() { gl_FragColor = texture2D(diffuseTexture, gl_PointCoord) * vColor; }`; @RE.Prop("Texture") texture @RE.Prop("Color") color = new Color(255, 255, 255) @RE.Prop("Number") alpha = 1 @RE.Prop("Select") blending = 0 blendingOptions = ["Additive", "Normal", "Multiply", "None", "Subtractive"] //this is the "mesh" that we draw the particle textures into //it's a THREE.Points object that I added underneath the GameLogic Object3d @RE.Prop("Object3D") globalPointsObject @RE.Prop("Number") max = 500 @RE.Prop("Number") spawnPerTick = 1 @RE.Prop("Number") lifespan = 2 //This is the Object3d where we spawn particles from. It uses the worldPosition //so things like rotations are accounted for @RE.Prop("Object3D") initialLocation @RE.Prop("Vector3") initialVelocity = new THREE.Vector3(0,0,0) //this is the "mesh" that we draw the particle textures into //it's a THREE.Points object that I added underneath the GameLogic Object3d @RE.Prop("Object3D") points @RE.Prop("Number") thrustScalar = 0.01 @RE.Prop("Vector3") positionRandomBounds = new THREE.Vector3(1,1,1) @RE.Prop("Vector3") velocityRandomBounds = new THREE.Vector3(1,1,1) start() { const bounds = RE.Runtime.rogueDOMContainer.getBoundingClientRect(); const uniforms = { diffuseTexture: { value: this.texture }, pointMultiplier: { value: bounds.width - bounds.left } } this.material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: this.vertexShader, fragmentShader: this.fragmentShader, blending: this.convertBlending(this.blending), depthTest: true, depthWrite: false, transparent: true, vertexColors: true, }) this.geometry = new THREE.BufferGeometry() this.geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3)); this.geometry.setAttribute('color', new THREE.Float32BufferAttribute([], 4)); this.globalPointsObject.geometry = this.geometry this.globalPointsObject.material = this.material this.particles = [] this.ship = RE.getComponent(Player, this.object3d) this.updateGeometry() } convertBlending(selectIndex) { console.log(selectIndex) switch(this.blendingOptions[selectIndex]) { case "Additive": return THREE.AdditiveBlending case "Normal": return THREE.NormalBlending case "Multiply": return THREE.MultiplyBlending case "None": return THREE.NoBlending case "Subtractive": return THREE.SubtractiveBlending default: return THREE.AdditiveBlending } } awake() { } update() { if(this.ship.isThrusting) { this.addParticles(); } this.updateParticles(RE.Runtime.deltaTime); this.updateGeometry(); } addParticles() { if(this.particles.length >= this.max) { return } for (let i = 0; i < this.spawnPerTick; i++) { const life = this.lifespan; const newPosition = new THREE.Vector3() this.initialLocation.getWorldPosition(newPosition) newPosition.add(new THREE.Vector3( 1 * Math.random() - 0.5, 1 * Math.random() - 0.5, 1 * Math.random() - 0.5 ).multiply(this.positionRandomBounds)) const newVelocity = this.initialVelocity.clone() if(this.ship) { newVelocity.add(new THREE.Vector3( -this.ship.bodyComponent.body.force.x, -this.ship.bodyComponent.body.force.y, -this.ship.bodyComponent.body.force.z) .multiplyScalar(this.thrustScalar)) } newVelocity.add(new THREE.Vector3( 1 * Math.random() - 0.5, 1 * Math.random() - 0.5, 1 * Math.random() - 0.5 ).multiply(this.velocityRandomBounds) ) this.particles.push({ position: newPosition, life: life, maxLife: life, velocity: newVelocity, color: this.color, alpha: this.alpha, }); } } updateGeometry() { const positions = []; const colors = [] for (let p of this.particles) { positions.push(p.position.x, p.position.y, p.position.z); colors.push(p.color.r, p.color.g, p.color.b, p.alpha); } this.geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); this.geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 4)); this.geometry.attributes.position.needsUpdate = true; this.geometry.attributes.color.needsUpdate = true; } updateParticles(deltaTime) { for (let p of this.particles) { p.life -= deltaTime; } this.particles = this.particles.filter(p => { return p.life > 0.0; }); for (let p of this.particles) { p.position.add(p.velocity.clone()); } // RE.Debug.log(JSON.stringify(this.particles[0].position)) let camera = RE.App.currentScene.getObjectByProperty('uuid', RE.App.activeCamera) this.particles.sort((a, b) => { const d1 = camera.position.distanceTo(a.position); const d2 = camera.position.distanceTo(b.position); if (d1 > d2) { return -1; } if (d1 < d2) { return 1; } return 0; }); } } RE.registerComponent(ParticleSystem)