import * as util from './utils.js'
import ThreeView from '../src/ThreeView.js'
import ColorMap from '../src/ColorMap.js'
import Color from './Color.js'
function isStaticColor(color) {
return !isDynamicColor(color)
}
function isDynamicColor(color) {
return (
color === 'random' || util.isImageable(color) || util.isFunction(color)
)
}
/**
* Basic 3D view.
*
* **TODO: Document this class**
*/
class ThreeDraw extends ThreeView {
static defaultOptions() {
return {
patchesMesh: null, // 'PatchesMesh',
turtlesMesh: null, // 'QuadSpritesMesh',
linksMesh: null, // 'LinksMesh',
patchesColor: 'random',
// patchesShape: 'point',
// patchesShape: null,
patchesSize: 1,
initPatches: null,
turtlesColor: 'random',
turtlesShape: 'dart',
turtlesSize: 1,
linksColor: 'random',
linksWidth: 1,
patchesMap: 'DarkGray',
turtlesMap: 'Basic16',
// textProperty: null,
// textSize: 0.5,
// textColor: 'black',
lastClearColor: null,
lastImage: null,
}
}
// ======================
constructor(model, viewOptions = {}, drawOptions = {}) {
// merge defaultOptions into drawOptions
if (viewOptions.drawOptions) {
drawOptions = viewOptions.drawOptions
delete viewOptions.drawOptions
}
drawOptions = Object.assign(ThreeDraw.defaultOptions(), drawOptions)
// Instantiate maps if only names given.
if (typeof drawOptions.turtlesMap === 'string')
drawOptions.turtlesMap = ColorMap[drawOptions.turtlesMap]
if (typeof drawOptions.patchesMap === 'string')
drawOptions.patchesMap = ColorMap[drawOptions.patchesMap]
// filter out meshes object from View & viewOptions overrides
const { patches, turtles, links } = Object.assign(
ThreeView.defaultOptions(),
viewOptions
)
const meshes = { patches, turtles, links }
// Sync meshes to drawOptions.
for (const mesh of ['patches', 'turtles', 'links']) {
// Add draw meshes to view
const meshName = mesh + 'Mesh'
if (drawOptions[meshName]) {
const option = drawOptions[meshName]
meshes[mesh] =
typeof option === 'string' ? { meshClass: option } : option
}
// If color is static, convert to typedColor
// & add to mesh for static static meshes & attributes
const color = mesh + 'Color'
if (isStaticColor(drawOptions[color])) {
drawOptions[color] = Color.toTypedColor(drawOptions[color])
meshes[mesh].color = drawOptions[color] // typed color
}
// Add static sizes to viewOptions for static meshes & attributes
const size = mesh + 'Size'
if (typeof drawOptions[size] === 'number') {
// && mesh != 'patches'
meshes[mesh].size = drawOptions[size]
}
}
// console.log('meshes', meshes)
// call View ctor, overriding mesh options derived above
Object.assign(viewOptions, meshes)
super(model.world, viewOptions)
console.log('viewOptions', viewOptions)
console.log('drawOptions', drawOptions)
console.log('meshes', meshes)
// Initialization for static patches:
if (this.meshName('patches') === 'PatchesMesh') {
if (drawOptions.initPatches) {
// colors is an array of typedColors or pixels:
const colors = drawOptions.initPatches(model, this) //view)
this.createPatchPixels(i => colors[i])
} else if (drawOptions.patchesColor === 'random') {
// NOTE: random colors only done once for patches.
this.createPatchPixels(i =>
drawOptions.patchesMap.randomColor()
)
}
}
// merge model, view, drawOptions into "this"
this.checkParams(drawOptions)
Object.assign(this, { model, view: this, drawOptions })
}
// The parameters are easily mistaken: check they are all in the defaults.
checkParams(params) {
const keys = Object.keys(params)
const defaults = ThreeDraw.defaultOptions()
keys.forEach(k => {
if (defaults[k] === undefined) {
console.log(
'Legal ThreeDraw parameters',
Object.keys(ThreeDraw.defaultOptions())
)
throw Error('Unknown ThreeDraw parameter: ' + k)
}
})
}
getMesh(agentSet) {
return this.meshes[agentSet]
}
meshName(agentSet) {
return this.meshes[agentSet].name
}
getColor(agent) {
const type = agent.agentSet.name
const colorName = type + 'Color'
const map =
type === 'patches'
? this.drawOptions['patchesMap']
: this.drawOptions['turtlesMap']
const color = this.drawOptions[colorName]
const result =
color === 'random'
? map.atIndex(agent.id)
: typeof color === 'function'
? Color.toTypedColor(color(agent))
: Color.toTypedColor(color)
return result
}
// getColor(agent, color, map = this.drawOptions.turtlesMap) {
// const result =
// color === 'random'
// ? map.atIndex(agent.id)
// : typeof color === 'function'
// ? Color.toTypedColor(color(agent))
// : Color.toTypedColor(color)
// return result
// }
// getShape(agent, shape) {
// const result = typeof shape === 'function' ? shape(agent) : shape
// return result
// }
// getSize(agent, size) {
// const result = typeof size === 'function' ? size(agent) : size
// return result
// }
draw() {
let {
patchesColor,
patchesShape,
patchesSize,
initPatches,
turtlesColor,
turtlesShape,
turtlesSize,
linksColor,
linksWidth,
patchesMap,
turtlesMap,
// textProperty,
// textSize,
// textColor,
lastClearColor,
} = this.drawOptions
const { model, view } = this
// color === 'random'
// ? map.atIndex(agent.id)
// : typeof color === 'function'
// ? checkColor(agent, color(agent))
// : color
// const checkColor = (agent, color, map = turtlesMap) =>
// color === 'random'
// ? map.atIndex(agent.id) //.css
// : Color.toTypedColor(color)
// Helpers for converting color, shape, size below
const getColor = (agent, color, map) =>
color === 'random'
? map.atIndex(agent.id)
: typeof color === 'function'
? Color.toTypedColor(color(agent))
: Color.toTypedColor(color)
const getShape = (agent, shape) =>
typeof shape === 'function' ? shape(agent) : shape
const getSize = (agent, size) =>
typeof size === 'function' ? size(agent) : size
// const { getColor, getShape, getSize } = this
// const { getShape, getSize } = this
// let lastImage, lastClearColor
if (this.meshName('patches') === 'PatchesMesh') {
if (patchesColor === 'random' || initPatches) {
// Already in gpu
} else if (typeof patchesColor === 'function') {
view.drawPatches(model.patches, p => patchesColor(p))
} else if (util.isImageable(patchesColor)) {
// Already in gpu?
if (patchesColor !== lastImage) {
view.drawPatchesImage(patchesColor)
lastImage = patchesColor
}
} else {
// Should be static color for clear() call
// Already in gpu?
if (patchesColor !== lastClearColor) {
this.drawOptions.lastClearColor = patchesColor
view.clearPatches(patchesColor)
}
}
} else {
view.drawPatches(model.patches, p => ({
shape: getShape(p, patchesShape),
color: getColor(p, patchesColor, patchesMap),
size: getSize(p, patchesSize),
}))
}
view.drawLinks(model.links, l => ({
color: getColor(l, linksColor, turtlesMap),
width: linksWidth,
}))
// REMIND: adjust for PointMesh
view.drawTurtles(model.turtles, t => ({
shape: getShape(t, turtlesShape),
color: getColor(t, turtlesColor, turtlesMap),
size: getSize(t, turtlesSize),
}))
// if (textProperty) {
// model.turtles.ask(t => {
// if (t[textProperty] != null)
// view.drawText(t[textProperty], t.x, t.y, textColor)
// })
// }
view.render() // calls three.render() & view.tick()
}
}
export default ThreeDraw