Skip to content

Commit

Permalink
Add line fading for proximity
Browse files Browse the repository at this point in the history
  • Loading branch information
alexburner committed Dec 31, 2019
1 parent 63351ab commit ade10c4
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 50 deletions.
6 changes: 4 additions & 2 deletions src/particles/System.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ export interface NeighborMsg {
distance: number
}

type NeighborhoodType = 'all' | 'locals' | 'nearest' | 'proximity'
export type NeighborhoodType = 'all' | 'locals' | 'nearest' | 'proximity'

/**
* Neighbor objects
*/
export interface NeighborhoodMsg {
type: NeighborhoodType
config?: any // TODO make this better
/**
* NeighborMsg[] for each particle
* sorted nearest -> furthest
Expand All @@ -36,7 +37,7 @@ export interface NeighborhoodMsg {

interface NeighborhoodSpec {
name: NeighborhoodType
config?: any // TODO - what do here
config?: any // TODO make this better
}

interface AllSpec extends NeighborhoodSpec {
Expand Down Expand Up @@ -151,6 +152,7 @@ export default class System {
case 'proximity':
return {
type: 'proximity',
config: spec.config,
neighbors: this.particles.map((particle: ParticleN) => {
const neighbors: NeighborN[] = []
for (let i = 0, l = particle.neighbors.length; i < l; i++) {
Expand Down
31 changes: 31 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,34 @@ export class RecentQueue<T> {
return this.queue
}
}

/**
* Create a fader function for proximity render
*/
const FADE_EDGE_SCALE = 0.2
export const createFaderFn = (
min: number,
max: number,
): ((distance: number, value: number) => number) => {
const proximity = max - min
const edge = FADE_EDGE_SCALE * proximity
const minEdgeMin = min
const minEdgeMax = min + edge
const maxEdgeMin = max - edge
const maxEdgeMax = max
return (distance: number, value: number): number => {
if (distance >= minEdgeMin && distance <= minEdgeMax) {
const magnitude = distance - minEdgeMin
const scale = magnitude / edge
const scaled = value * scale
return scaled
}
if (distance >= maxEdgeMin && length <= maxEdgeMax) {
const magnitude = edge - (distance - maxEdgeMin)
const scale = magnitude / edge
const scaled = value * scale
return scaled
}
return value
}
}
53 changes: 34 additions & 19 deletions src/view/layers/Circles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as THREE from 'three'

import Particle3, { toVector3 } from 'src/particles/Particle3'
import { NeighborhoodMsg, NeighborMsg } from 'src/particles/System'
import { createFaderFn } from 'src/util'
import { clearObjList, Layer, LayerArgs, resizeObjList } from 'src/view/Layers'

const DIVISIONS = 64
Expand All @@ -12,7 +13,6 @@ export default class Circles implements Layer {
private readonly group: THREE.Group
private objects: THREE.Object3D[]
private readonly geometry: THREE.BufferGeometry
private readonly material: THREE.LineBasicMaterial

constructor(group: THREE.Group) {
this.group = group
Expand All @@ -26,12 +26,6 @@ export default class Circles implements Layer {
)
this.geometry = new THREE.BufferGeometry()
this.geometry.setFromPoints(points3)
this.material = new THREE.LineBasicMaterial({
blending: THREE.AdditiveBlending,
transparent: true,
color: 0xffffff,
opacity: OPACITY,
})
}

public update({ particles, neighborhood }: LayerArgs): void {
Expand All @@ -43,7 +37,16 @@ export default class Circles implements Layer {
group: this.group,
currList: this.objects,
newSize: specs.length,
createObj: (): THREE.Line => new THREE.Line(this.geometry, this.material),
createObj: (): THREE.Line =>
new THREE.Line(
this.geometry,
new THREE.LineBasicMaterial({
blending: THREE.AdditiveBlending,
transparent: true,
color: 0xffffff,
opacity: OPACITY,
}),
),
})

// 3. Update objects to match specs
Expand All @@ -59,31 +62,43 @@ interface CircleSpec {
delta: THREE.Vector3
position: THREE.Vector3
radius: number
opacity: number
}

const makeCircleSpecs = (
particles: Particle3[],
neighborhood: NeighborhoodMsg,
): CircleSpec[] =>
particles.reduce((memo: CircleSpec[], particle: Particle3, i: number) => {
neighborhood.neighbors[i].forEach((neighbor: NeighborMsg) => {
memo.push({
delta: toVector3(neighbor.delta),
position: particle.position,
radius: neighbor.distance,
): CircleSpec[] => {
const fader =
neighborhood.type === 'proximity'
? createFaderFn(neighborhood.config.min, neighborhood.config.max)
: undefined
return particles.reduce(
(memo: CircleSpec[], particle: Particle3, i: number) => {
neighborhood.neighbors[i].forEach((neighbor: NeighborMsg) => {
memo.push({
delta: toVector3(neighbor.delta),
position: particle.position,
radius: neighbor.distance,
opacity: fader ? fader(neighbor.distance, OPACITY) : OPACITY,
})
})
})
return memo
}, [] as CircleSpec[])
return memo
},
[] as CircleSpec[],
)
}

const updateCircles = (specs: CircleSpec[], objects: THREE.Object3D[]): void =>
specs.forEach((spec: CircleSpec, i: number) => {
const object = objects[i]
const object = objects[i] as THREE.Line
object.position.x = spec.position.x
object.position.y = spec.position.y
object.position.z = spec.position.z
object.scale.set(spec.radius, spec.radius, spec.radius)
// Rotate circle to align with delta vector
// via https://stackoverflow.com/a/31987883/3717556
object.quaternion.setFromUnitVectors(AXIS, spec.delta.clone().normalize())
const material = object.material as THREE.LineBasicMaterial
material.opacity = spec.opacity
})
32 changes: 23 additions & 9 deletions src/view/layers/Lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import * as THREE from 'three'

import { MAX_NEIGHBORS } from 'src/constants'
import Particle3 from 'src/particles/Particle3'
import { NeighborMsg } from 'src/particles/System'
import { NeighborhoodType, NeighborMsg } from 'src/particles/System'
import { createFaderFn } from 'src/util'
import { Layer, LayerArgs } from 'src/view/Layers'

const POINT_COUNT = MAX_NEIGHBORS * 3
Expand All @@ -18,6 +19,8 @@ export default class Lines implements Layer {
private readonly alphas: Float32Array
private readonly alphaAttr: THREE.BufferAttribute

private prevNeighborType?: NeighborhoodType

constructor(group: THREE.Group) {
this.geometry = new THREE.BufferGeometry()

Expand All @@ -44,12 +47,13 @@ export default class Lines implements Layer {
depthTest: false,
}),
)

group.add(this.lineSegments)
}

public update({ particles, neighborhood }: LayerArgs): void {
let posIndex = 0
let numConnected = 0
const neighbors: NeighborMsg[] = []
particles.forEach((particle: Particle3, i: number) => {
neighborhood.neighbors[i].forEach((neighbor: NeighborMsg) => {
const other = particles[neighbor.index]
Expand All @@ -59,23 +63,33 @@ export default class Lines implements Layer {
this.positions[posIndex++] = other.position.x
this.positions[posIndex++] = other.position.y
this.positions[posIndex++] = other.position.z
numConnected++
neighbors.push(neighbor)
})
})
this.posAttr.needsUpdate = true

const drawRange = neighbors.length * 2

const drawRange = numConnected * 2
this.geometry.setDrawRange(0, drawRange)

if (neighborhood.type === 'proximity') {
// set each line to fade based on length
// TODO
} else {
// set all lines to default opacity
const fader = createFaderFn(
neighborhood.config.min,
neighborhood.config.max,
)
for (let i = 0; i < drawRange; i++) {
const neighbor = neighbors[Math.floor(i / 2)]
this.alphas[i] = fader(neighbor.distance, OPACITY)
this.alphaAttr.needsUpdate = true
}
} else if (this.prevNeighborType === 'proximity') {
// reset all lines to default opacity
this.alphas.fill(OPACITY, 0, drawRange)
this.alphaAttr.needsUpdate = true
}

this.posAttr.needsUpdate = true
this.alphaAttr.needsUpdate = true
this.prevNeighborType = neighborhood.type
}

public clear(): void {
Expand Down
60 changes: 40 additions & 20 deletions src/view/layers/Spheres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import * as THREE from 'three'

import Particle3 from 'src/particles/Particle3'
import { NeighborhoodMsg, NeighborMsg } from 'src/particles/System'
import { createFaderFn } from 'src/util'
import { clearObjList, Layer, LayerArgs, resizeObjList } from 'src/view/Layers'

const OPACITY_MAX = 0.5
const OPACITY_MAX = 0.6
const OPACITY_MIN = 0.1
const SEGMENTS = 40
const RINGS = 40
Expand All @@ -13,18 +14,11 @@ export default class Spheres implements Layer {
private readonly group: THREE.Group
private objects: THREE.Object3D[]
private readonly geometry: THREE.SphereBufferGeometry
private readonly material: THREE.MeshNormalMaterial

constructor(group: THREE.Group) {
this.group = group
this.objects = []
this.geometry = new THREE.SphereBufferGeometry(1, SEGMENTS, RINGS)
this.material = new THREE.MeshNormalMaterial({
blending: THREE.AdditiveBlending,
depthTest: false,
opacity: OPACITY_MAX,
transparent: true,
})
}

public update({ particles, neighborhood }: LayerArgs): void {
Expand All @@ -36,7 +30,16 @@ export default class Spheres implements Layer {
group: this.group,
currList: this.objects,
newSize: specs.length,
createObj: (): THREE.Mesh => new THREE.Mesh(this.geometry, this.material),
createObj: (): THREE.Mesh =>
new THREE.Mesh(
this.geometry,
new THREE.MeshNormalMaterial({
blending: THREE.AdditiveBlending,
depthTest: false,
opacity: OPACITY_MAX,
transparent: true,
}),
),
})

// 3. Update objects to match specs
Expand All @@ -51,35 +54,52 @@ export default class Spheres implements Layer {
interface SphereSpec {
position: THREE.Vector3
radius: number
opacity: number
}

const makeSphereSpecs = (
particles: Particle3[],
neighborhood: NeighborhoodMsg,
): SphereSpec[] =>
particles.reduce((memo: SphereSpec[], particle: Particle3, i: number) => {
neighborhood.neighbors[i].forEach((neighbor: NeighborMsg) => {
memo.push({
position: particle.position,
radius: neighbor.distance,
): SphereSpec[] => {
const allNeighbors = neighborhood.neighbors.reduce(
(memo: NeighborMsg[], neighbors: NeighborMsg[]): NeighborMsg[] => {
memo.push(...neighbors)
return memo
},
[],
)
const opacity = getOpacity(allNeighbors.length)
const fader =
neighborhood.type === 'proximity'
? createFaderFn(neighborhood.config.min, neighborhood.config.max)
: undefined
return particles.reduce(
(memo: SphereSpec[], particle: Particle3, i: number) => {
neighborhood.neighbors[i].forEach((neighbor: NeighborMsg) => {
memo.push({
position: particle.position,
radius: neighbor.distance,
opacity: fader ? fader(neighbor.distance, opacity) : opacity,
})
})
})
return memo
}, [])
return memo
},
[],
)
}

const updateSpheres = (
specs: SphereSpec[],
objects: THREE.Object3D[],
): void => {
const opacity = getOpacity(specs.length)
specs.forEach((spec: SphereSpec, i: number) => {
const object = objects[i] as THREE.Mesh
object.position.x = spec.position.x
object.position.y = spec.position.y
object.position.z = spec.position.z
object.scale.set(spec.radius, spec.radius, spec.radius)
const material = object.material as THREE.MeshNormalMaterial
material.opacity = opacity
material.opacity = spec.opacity
})
}

Expand Down

0 comments on commit ade10c4

Please sign in to comment.