iv-3d-lib
Version:
A reusable ThreeJS library providing features for Ivee 3D Editor.
357 lines (331 loc) • 17.7 kB
text/typescript
import * as THREE from 'three';
import * as parser from 'expr-eval';
import * as numeral from 'numeral';
import { WglUtil } from './wgl-util';
import * as cfg from './wgl-util-cfgs';
import { ITimelinePlugin } from './i-timeline-plugin';
import { SimpleDictionary } from './models/simple-dictionary';
import { Timeline, DisplacementXYZ, TimelineMesh } from './models/timeline';
import { ErrorService } from './services/error-service';
import { DataModel } from './models/data-model';
import { Iv3dDataSource, Iv3dDataSourceItem, Iv3dDataSourceValueFormat, Iv3dDataSourceUrl } from './models/iv-3d-data-source';
import { Iv3dObject, Iv3dObjectType, Iv3dGeometryType } from './models/iv-3d-object';
import { Iv3dObjectHandler } from './iv-3d-object-handler';
export class DataSourcePlugin implements ITimelinePlugin {
config: SimpleDictionary<any>;
timeline: Timeline;
data: DataModel;
w: WglUtil;
mainGroup: THREE.Group;
constructor(private dataSource: Iv3dDataSource,
private objHandler: Iv3dObjectHandler,
private demo: boolean,
private isPublic: boolean
) {
}
init() {
}
load() {
this.loadDataSource();
}
update(time?: number) {
}
private loadDataSource() {
if (this.dataSource && this.dataSource.items) {
/*
if (!this.dataSource.urls || this.dataSource.urls.length === 0) {
this.dataSource.urls = [];
for (let i = 0; i < 3; i++) {
let dsUrl: Iv3dDataSourceUrl = new Iv3dDataSourceUrl();
let urlProp = this.objHandler.getProp(`url${i}`, this.data.dataSourceProps);
if (i === this.currentDataSourceIndex) {
dsUrl.url = this.dataSource.url;
}
else {
dsUrl.url = urlProp ? urlProp.value : '';
}
dsUrl.patternUrl = urlProp ? urlProp.value : '';
let titleProp = this.objHandler.getProp(`title${i}`, this.data.dataSourceProps);
dsUrl.title = titleProp ? titleProp.value : '';
let typeProp = this.objHandler.getProp(`type${i}`, this.data.dataSourceProps);
dsUrl.type = typeProp ? typeProp.value : '';
this.dataSource.urls.push(dsUrl);
}
}
*/
let valueFormatProp = this.objHandler.getProp('valueFormat', this.data.dataSourceProps);
this.dataSource.valueFormat = valueFormatProp ? valueFormatProp.value : '';
let dataItems: Iv3dDataSourceItem[] = this.dataSource.items; //.filter(item => item.value > 1000);
let values: number[] = dataItems.map(item => item.value);
let minN = Math.min(...values);
minN = minN < 0 ? 2 * minN : 0;
let maxN = Math.max(...values);
maxN = maxN > 0 ? 2 * maxN : 0;
// let titleProp = this.objHandler.getProp('title', this.data.dataSourceProps);
// this.dataSource.title = titleProp ? titleProp.value : '';
// let dsTypeProp = this.objHandler.getProp('type', this.data.dataSourceProps);
let dsBackgroundColorProp = this.objHandler.getProp('textBackgroundColor', this.data.dataSourceProps);
let dsTransparentBackgroundProp = this.objHandler.getProp('transparentBackground', this.data.dataSourceProps);
if (dsTransparentBackgroundProp && dsTransparentBackgroundProp.value) {
this.dataSource.textBackgroundColor = 'transparent';
}
else {
this.dataSource.textBackgroundColor = dsBackgroundColorProp ? dsBackgroundColorProp.value : '#FFF';
}
let dsTextColorProp = this.objHandler.getProp('textColor', this.data.dataSourceProps);
this.dataSource.textColor = dsTextColorProp ? dsTextColorProp.value : '#000';
this.dataSource.panelCss = this.objHandler.getCssCfg(this.data.dataSourceProps);
let pageProp = this.objHandler.getProp('page', this.data.dataSourceProps);
if (pageProp && pageProp.value) {
this.dataSource.page = pageProp.value;
}
let pageSizeProp = this.objHandler.getProp('pageSize', this.data.dataSourceProps);
if (pageSizeProp && pageSizeProp.value) {
this.dataSource.pageSize = pageSizeProp.value;
}
// let drilldownPatternUrlProp = this.objHandler.getProp('drilldownPatternUrl', this.data.dataSourceProps);
// this.dataSource.drilldownPatternUrl = drilldownPatternUrlProp ? drilldownPatternUrlProp.value : '';
// this.dataSource.dataSourceIndex = this.currentDataSourceIndex;
// let dsType = this.dataSource.urls[this.currentDataSourceIndex] ? this.dataSource.urls[this.currentDataSourceIndex].type : Iv3dObjectType.cube;
let dsType = this.dataSource.type;
for (let dataItem of dataItems) {
switch (dsType) {
case Iv3dObjectType.sphere:
this.bindDataToSphere(dataItem, '', minN, maxN);
break;
case Iv3dObjectType.particles:
this.bindDataToParticles(dataItem, '', minN, maxN);
break;
case Iv3dObjectType.torus:
this.bindDataToTorus(dataItem, '', minN, maxN);
break;
case Iv3dObjectType.torusKnot:
this.bindDataToTorusKnot(dataItem, '', minN, maxN);
break;
case Iv3dObjectType.cylinder:
this.bindDataToCylinder(dataItem, '', minN, maxN);
break;
case Iv3dObjectType.cube:
default:
this.bindDataToCube(dataItem, '', minN, maxN);
break;
}
}
}
}
private bindDataToSphere(data: Iv3dDataSourceItem, key: string, minN?: number, maxN?: number) {
let label = data.label;
let value = data.value;
let obj: Iv3dObject = this.objHandler.createDefaultObject(Iv3dObjectType.sphere, Iv3dGeometryType.sphere);
obj.dataSourceItem = data;
obj.script = 'onMeshClick(mesh)';
this.data.container.children.push(obj);
let radiusProp = this.objHandler.getProp('radius', obj.geometryProps);
let v = this.scaleValue(value, minN, maxN);
radiusProp.value = v;
this.bindDataToObject(obj, data, key);
}
private bindDataToCube(data: Iv3dDataSourceItem, key: string, minN?: number, maxN?: number) {
let label = data.label;
let value = data.value;
let obj: Iv3dObject = this.objHandler.createDefaultObject(Iv3dObjectType.cube, Iv3dGeometryType.box);
obj.dataSourceItem = data;
obj.script = 'onMeshClick(mesh)';
this.data.container.children.push(obj);
let widthProp = this.objHandler.getProp('width', obj.geometryProps);
let heightProp = this.objHandler.getProp('height', obj.geometryProps);
let depthProp = this.objHandler.getProp('depth', obj.geometryProps);
let v = this.scaleValue(value, minN, maxN);
widthProp.value = v;
heightProp.value = v;
depthProp.value = v;
this.bindDataToObject(obj, data, key);
}
private bindDataToTorus(data: Iv3dDataSourceItem, key: string, minN?: number, maxN?: number) {
let label = data.label;
let value = data.value;
let obj: Iv3dObject = this.objHandler.createDefaultObject(Iv3dObjectType.torus, Iv3dGeometryType.torus);
obj.dataSourceItem = data;
obj.script = 'onMeshClick(mesh)';
this.data.container.children.push(obj);
let radiusProp = this.objHandler.getProp('radius', obj.geometryProps);
let tubeProp = this.objHandler.getProp('tube', obj.geometryProps);
let v = this.scaleValue(value, minN, maxN);
radiusProp.value = v;
tubeProp.value = v / 2;
this.bindDataToObject(obj, data, key);
}
private bindDataToTorusKnot(data: Iv3dDataSourceItem, key: string, minN?: number, maxN?: number) {
let label = data.label;
let value = data.value;
let obj: Iv3dObject = this.objHandler.createDefaultObject(Iv3dObjectType.torusKnot, Iv3dGeometryType.torusKnot);
obj.dataSourceItem = data;
obj.script = 'onMeshClick(mesh)';
this.data.container.children.push(obj);
let radiusProp = this.objHandler.getProp('radius', obj.geometryProps);
let tubeProp = this.objHandler.getProp('tube', obj.geometryProps);
let v = this.scaleValue(value, minN, maxN);
radiusProp.value = v;
tubeProp.value = v / 4;
this.bindDataToObject(obj, data, key);
}
private bindDataToCylinder(data: Iv3dDataSourceItem, key: string, minN?: number, maxN?: number) {
let label = data.label;
let value = data.value;
let obj: Iv3dObject = this.objHandler.createDefaultObject(Iv3dObjectType.cylinder, Iv3dGeometryType.cylinder);
obj.dataSourceItem = data;
obj.script = 'onMeshClick(mesh)';
this.data.container.children.push(obj);
let radiusTopProp = this.objHandler.getProp('radiusTop', obj.geometryProps);
let radiusBottomProp = this.objHandler.getProp('radiusBottom', obj.geometryProps);
let heightProp = this.objHandler.getProp('height', obj.geometryProps);
let v = this.scaleValue(value, minN, maxN);
radiusTopProp.value = v;
radiusBottomProp.value = v;
heightProp.value = v / 2;
this.bindDataToObject(obj, data, key);
}
private bindDataToParticles(data: Iv3dDataSourceItem, key: string, minN?: number, maxN?: number) {
let label = data.label;
let value = data.value;
let obj: Iv3dObject = this.objHandler.createDefaultObject(Iv3dObjectType.particles, Iv3dGeometryType.particles);
obj.dataSourceItem = data;
obj.script = 'onMeshClick(mesh)';
this.data.container.children.push(obj);
let countProp = this.objHandler.getProp('count', obj.geometryProps);
let rangeProp = this.objHandler.getProp('range', obj.geometryProps);
let v = this.scaleValue(value, minN, maxN);
let stdCountProp = this.objHandler.getProp('count', this.objHandler.particleGeomProps);
let count = this.scaleValue(value, stdCountProp.min, stdCountProp.max);
countProp.value = value;
rangeProp.value = v;
this.bindDataToObject(obj, data, key);
}
private scaleValue(value: number, minN?: number, maxN?: number): number {
let nearProp = this.objHandler.getProp('near', this.data.cameraProps);
let farProp = this.objHandler.getProp('far', this.data.cameraProps);
let fovProp = this.objHandler.getProp('fov', this.data.cameraProps);
if (minN === maxN) minN = 0;
let min = minN;
let max = maxN;
let a = nearProp.value / fovProp.value;
let b = farProp.value / fovProp.value;
let v = (b - a) * (value - min) / (max - min) + a;
return v;
}
private interpolate(x: number, minN: number, maxN: number): number {
return minN * (1 - x) + maxN * x;
}
private uninterpolate(x: number, a: number, b: number): number {
let factor = (b - a) != 0 ? b - a : 1 / b;
return (x - a) / factor;
}
public scaleValueXYZ(x: number, minN: number, maxN: number) {
let nearProp = this.objHandler.getProp('near', this.data.cameraProps);
let farProp = this.objHandler.getProp('far', this.data.cameraProps);
let fixMin = 1;
let fixMax = 1000000;
let min = cfg.U.isEmpty(minN) ? fixMin : (minN < fixMin ? minN : fixMin);
let max = cfg.U.isEmpty(maxN) ? fixMax : (maxN > fixMax ? maxN : fixMax);
let a = nearProp.value;
let b = farProp.value;
return this.interpolate(this.uninterpolate(x, a, b), min, max);
}
private bindDataToObject(obj: Iv3dObject, data: Iv3dDataSourceItem, key: string) {
let label = data.label;
let value = data.value;
let useCanvasProp = this.objHandler.getProp('useCanvas', obj.materialProps);
useCanvasProp.value = false;
let textProp = this.objHandler.getProp('text', obj.materialProps);
textProp.value = value;
let colorProp = this.objHandler.getProp('color', obj.materialProps);
colorProp.value = '#' + (Math.random() * 0xFFFFFF).toString(16);
let matTypeProp = this.objHandler.getProp('type', obj.materialProps);
if (obj.meshType === Iv3dObjectType.particles) {
matTypeProp.value = 'PointsMaterial';
}
else {
matTypeProp.value = 'MeshPhongMaterial';
}
this.objHandler.loadObject(obj, this.w, this.mainGroup, this.demo, this.isPublic, key, (threeObj) => {
var r = Math.random();
var vx = r > 0.5 ? +1 : -1;
r = Math.random();
var vy = r < 0.5 ? +1 : -1;
r = Math.random();
var vz = r > 0.5 ? +1 : -1;
let aspectProp = this.objHandler.getProp('aspect', this.data.cameraProps);
let fovProp = this.objHandler.getProp('fov', this.data.cameraProps);
let q = aspectProp.value * fovProp.value;
var qX = 3 * q, qY = 2 * q, qZ = 3 * q;
var px = Math.random() * qX * vx, py = Math.random() * qY * vy, pz = Math.random() * qZ * vz;
threeObj.position.set(px, py, pz);
//threeObj.geometry.computeBoundingBox();
//let bbox = threeObj.geometry.boundingBox;
//let sy = py < 0 ? bbox.max.y + 10 : bbox.min.y + 10;
let bbox = new THREE.Box3().setFromObject(threeObj);
let sx = bbox.max.x; let sy = bbox.max.y + 10; let sz = bbox.max.z;
let spriteCfg: cfg.SpriteCfg = <cfg.SpriteCfg>{ color: 0x0, position: <cfg.XYZ>{ x: px, y: sy, z: pz } };
let sValue = '';
if (this.dataSource.valueFormat === Iv3dDataSourceValueFormat.currency)
sValue = `${numeral(data.value).format('($0,0.00)')}`;
else if (this.dataSource.valueFormat === Iv3dDataSourceValueFormat.number)
sValue = `${numeral(data.value).format('(0,0.00)')}`;
else
sValue = `${data.value}`;
let label = `${data.label}: ${sValue}`;
let textWidth = label.length * 10;
let textHeight = 20;
let fontSize = 12;
let textureCfg: cfg.TextureCfg = <cfg.TextureCfg>{
canvas: <cfg.CanvasCfg>{
text: label
, width: textWidth, height: textHeight
, backgroundColor: this.dataSource.textBackgroundColor, textColor: this.dataSource.textColor, fontSize: fontSize, font: `${fontSize}pt Arial`, textAlign: 'center', textBaseline: 'middle'
}
};
let spriteTextObj = this.w.addTextSprite(spriteCfg, textureCfg, this.mainGroup);
spriteTextObj.objectId = threeObj.uuid;
threeObj.spriteId = spriteTextObj.uuid;
threeObj.spriteParentId = spriteTextObj.parent.uuid;
// sprite.scale.set(20, 10, 10);
let attemptsCount = 10;
let attempt = 0;
while (attempt < attemptsCount && this.w.checkObjectOverlap(threeObj, this.mainGroup.children)) {
px = Math.random() * qX * vx;
py = Math.random() * qY * vy;
pz = Math.random() * qZ * vz;
threeObj.position.set(px, py, pz);
//threeObj.geometry.computeBoundingBox();
//let bbox = threeObj.geometry.boundingBox;
//let sy = py < 0 ? bbox.max.y + 10 : bbox.min.y + 10;
let bbox = new THREE.Box3().setFromObject(threeObj);
let sx = bbox.max.x; let sy = bbox.max.y + 10; let sz = bbox.max.z;
spriteTextObj.position.set(px, sy, pz);
attempt++;
}
});
}
public filterDataSourceItems(event) {
let filterText: string = event.filterText;
let minVal: number = event.minVal;
let maxVal: number = event.maxVal;
let filteredItems: Iv3dDataSourceItem[] = event.filteredItems;
for (let threeObj of this.mainGroup.children) {
let obj: Iv3dObject = <Iv3dObject>threeObj.userData;
if (obj) {
let item: Iv3dDataSourceItem = obj.dataSourceItem;
threeObj.visible = (item && filteredItems.indexOf(item) >= 0);
let spriteParentObj = this.find3dObjectById(threeObj.spriteParentId);
if (spriteParentObj) {
spriteParentObj.visible = threeObj.visible;
spriteParentObj.children[0].visible = threeObj.visible;
}
}
}
}
private find3dObjectById(uuid: string) {
let obj = this.mainGroup.children.find(item => item.uuid === uuid);
return obj;
}
}