first test

This commit is contained in:
casperlamboo 2017-11-18 21:21:16 +01:00
parent da7cdfe350
commit a60f1970d5
11 changed files with 176 additions and 33 deletions

View File

@ -57,6 +57,22 @@ window.addEventListener('dragover', (event) => {
event.preventDefault(); event.preventDefault();
}); });
import * as CAL from 'cal';
store.dispatch(actions.addObject({
type: 'STAR',
fill: true,
solid: false,
star: { innerRadius: 10, outerRadius: 20, rays: 5 },
transform: new CAL.Matrix({ x: -20, y: 0 })
}));
store.dispatch(actions.addObject({
type: 'RECT',
fill: true,
rectSize: new CAL.Vector(20, 20),
height: 40,
transform: new CAL.Matrix({ x: -10, y: -10 })
}));
// render dom // render dom
import React from 'react'; import React from 'react';

View File

@ -403,6 +403,18 @@ style.innerHTML = `
width: 33px; width: 33px;
height: 41px; height: 41px;
} }
#hole-toggle-solid, #hole-toggle-solid-menu {
background-image: url('../img/contextmenu/btnShapeFill.png');
background-size: 33px auto;
width: 33px;
height: 41px;
}
#hole-toggle-hole, #hole-toggle-hole-menu {
background-image: url('../img/contextmenu/btnShapeOutline.png');
background-size: 33px auto;
width: 33px;
height: 41px;
}
#align-right-menu, #align-horizontal-menu, #align-left-menu, #align-right-menu, #align-horizontal-menu, #align-left-menu,
#align-top-menu, #align-vertical-menu, #align-bottom-menu { #align-top-menu, #align-vertical-menu, #align-bottom-menu {
background-image: url('../img/contextmenu/btnAlignHorizontal.png'); background-image: url('../img/contextmenu/btnAlignHorizontal.png');

View File

@ -4,6 +4,7 @@ export const COLOR_PICKER = 'color-picker-tool';
export const ERASER_SIZE = 'eraser-size-tool'; export const ERASER_SIZE = 'eraser-size-tool';
export const BRUSH_SIZE = 'brush-size-tool'; export const BRUSH_SIZE = 'brush-size-tool';
export const FILL_TOGGLE = 'fill-toggle-tool'; export const FILL_TOGGLE = 'fill-toggle-tool';
export const HOLE_TOGGLE = 'hole-toggle-tool';
export const ALIGN = 'align-tool'; export const ALIGN = 'align-tool';
export const ADVANCED = 'advanced-tool'; export const ADVANCED = 'advanced-tool';
@ -63,6 +64,14 @@ export const FILL_TOGGLE_TOOLS = [
FILL_TOGGLE_OUTLINE FILL_TOGGLE_OUTLINE
]; ];
export const HOLE_TOGGLE_HOLE = 'hole-toggle-hole';
export const HOLE_TOGGLE_SOLID = 'hole-toggle-solid';
export const HOLE_TOGGLE_TOOLS = [
HOLE_TOGGLE_HOLE,
HOLE_TOGGLE_SOLID
];
export const ALIGN_LEFT = 'align-left'; export const ALIGN_LEFT = 'align-left';
export const ALIGN_HORIZONTAL = 'align-horizontal'; export const ALIGN_HORIZONTAL = 'align-horizontal';
export const ALIGN_RIGHT = 'align-right'; export const ALIGN_RIGHT = 'align-right';

View File

@ -80,6 +80,11 @@ const context = {
selected: contextTools.FILL_TOGGLE_FILL, selected: contextTools.FILL_TOGGLE_FILL,
children: contextTools.FILL_TOGGLE_TOOLS.map(value => ({ value })), children: contextTools.FILL_TOGGLE_TOOLS.map(value => ({ value })),
...toggleBehavior ...toggleBehavior
}, {
value: contextTools.HOLE_TOGGLE,
selected: contextTools.HOLE_TOGGLE_SOLID,
children: contextTools.HOLE_TOGGLE_TOOLS.map(value => ({ value })),
...toggleBehavior
}, { }, {
value: contextTools.ALIGN, value: contextTools.ALIGN,
selected: contextTools.ALIGN_HORIZONTAL, selected: contextTools.ALIGN_HORIZONTAL,

View File

@ -20,7 +20,8 @@ const defaultProperties = {
z: 0.0, z: 0.0,
sculpt: [{ pos: 0.0, scale: 1.0 }, { pos: 1.0, scale: 1.0 }], sculpt: [{ pos: 0.0, scale: 1.0 }, { pos: 1.0, scale: 1.0 }],
twist: 0.0, twist: 0.0,
fill : false fill: false,
solid: true
}; };
export const SHAPE_TYPE_PROPERTIES = { export const SHAPE_TYPE_PROPERTIES = {

View File

@ -1,4 +1,4 @@
import { shapeDataToShape } from '../shape/shapeDataUtils.js'; import { shapeDataToShape, determineActiveShape } from '../shape/shapeDataUtils.js';
// import R from 'ramda'; // import R from 'ramda';
export default class ShapesManager { export default class ShapesManager {
@ -14,10 +14,8 @@ export default class ShapesManager {
update(state) { update(state) {
const needRender = { active: false, inactive: false }; const needRender = { active: false, inactive: false };
const selectedObjects = state.selection.objects.map(({ id }) => id);
// determine if shape is "active", meaning it will be updated frequently // determine if shape is "active", meaning it will be updated frequently
const activeShapes = Object.keys(state.objectsById) const activeShapes = determineActiveShape(state);
.filter(id => state.d2.activeShape === id || selectedObjects.indexOf(id) !== -1);
const { objectsById } = state; const { objectsById } = state;
@ -45,7 +43,7 @@ export default class ShapesManager {
const newInactiveObjectUIDs = []; const newInactiveObjectUIDs = [];
for (const UID of spaceObjectIds) { for (const UID of spaceObjectIds) {
const active = activeShapes.indexOf(UID) !== -1; const active = activeShapes[UID];
if (active) { if (active) {
newActiveObjectUIDs.push(UID); newActiveObjectUIDs.push(UID);
} else { } else {

95
src/d3/ShapeMesh.js vendored
View File

@ -4,6 +4,9 @@ import { shapeToPointsCornered } from '../shape/shapeToPoints.js';
import * as THREE from 'three'; import * as THREE from 'three';
import { getPointsBounds, shapeChanged } from '../shape/shapeDataUtils.js'; import { getPointsBounds, shapeChanged } from '../shape/shapeDataUtils.js';
import { DESELECT_TRANSPARENCY, LEGACY_HEIGHT_STEP } from '../constants/d3Constants.js'; import { DESELECT_TRANSPARENCY, LEGACY_HEIGHT_STEP } from '../constants/d3Constants.js';
import ThreeBSP from 'three-js-csg';
const THREE_BSP = ThreeBSP(THREE);
const MAX_HEIGHT_BASE = 5; const MAX_HEIGHT_BASE = 5;
// Legacy compensation. Compensating for the fact that we // Legacy compensation. Compensating for the fact that we
@ -12,8 +15,11 @@ const MAX_HEIGHT_BASE = 5;
// and converting old files on open once // and converting old files on open once
const isValidNumber = (num) => typeof num === 'number' && !isNaN(num); const isValidNumber = (num) => typeof num === 'number' && !isNaN(num);
class ShapeMesh extends THREE.Mesh { class ShapeMesh extends THREE.Object3D {
constructor(shapeData, toonShader) { constructor(shapeData, holes, toonShader, active) {
super();
this.name = shapeData.UID;
const { sculpt, rotate, twist, height, type, transform, z, color, fill } = shapeData; const { sculpt, rotate, twist, height, type, transform, z, color, fill } = shapeData;
let material; let material;
@ -30,12 +36,16 @@ class ShapeMesh extends THREE.Mesh {
}); });
} }
super(new THREE.BufferGeometry(), material); this._mesh = new THREE.Mesh(new THREE.BufferGeometry(), material);
this._mesh.name = shapeData.UID;
this.add(this._mesh);
this._holeMesh = new THREE.Mesh(new THREE.Geometry, material);
this._holeMesh.name = shapeData.UID;
this.add(this._holeMesh);
this._toonShader = toonShader; this._toonShader = toonShader;
this.name = shapeData.UID;
this._shapes = []; this._shapes = [];
this._shapesMap = []; this._shapesMap = [];
@ -52,10 +62,49 @@ class ShapeMesh extends THREE.Mesh {
this._color = color; this._color = color;
this._fill = fill; this._fill = fill;
this.visible = shapeData.solid;
this.updatePoints(shapeData); this.updatePoints(shapeData);
this._checkHoles(holes, active);
} }
update(shapeData) { _checkHoles(holes, active) {
if (active) {
this._holeMesh.visible = false;
this._mesh.visible = true;
return false;
}
holes = holes.map(hole => hole.mesh._mesh);
const objectBoundingBox = new THREE.Box3().setFromObject(this._mesh);
holes = holes.filter(hole => {
const holeBoundingBox = new THREE.Box3().setFromObject(hole);
return holeBoundingBox.intersectsBox(objectBoundingBox);
});
if (holes.length === 0) {
this._holeMesh.visible = false;
this._mesh.visible = true;
return false;
}
// is coliding with holes
const objectGeometry = new THREE.Geometry().fromBufferGeometry(this._mesh.geometry);
let objectBSP = new THREE_BSP(objectGeometry);
for (const hole of holes) {
const holeGeometry = new THREE.Geometry().fromBufferGeometry(hole.geometry);
const holeBSP = new THREE_BSP(holeGeometry)
objectBSP = objectBSP.subtract(holeBSP)
}
this._holeMesh.geometry = objectBSP.toMesh().geometry;
this._holeMesh.visible = true;
this._mesh.visible = false;
return true;
}
update(shapeData, holes, active) {
let changed = false; let changed = false;
if (shapeChanged(this._shapeData, shapeData)) { if (shapeChanged(this._shapeData, shapeData)) {
@ -88,17 +137,27 @@ class ShapeMesh extends THREE.Mesh {
changed = true; changed = true;
} }
if (!shapeData.solid) {
this.visible = shapeData.solid || active;
this.setOpaque(true);
changed = true;
}
if (this._checkHoles(holes, active)) {
changed = true;
}
this._shapeData = shapeData; this._shapeData = shapeData;
return changed; return changed;
} }
setOpaque(opaque) { setOpaque(opaque) {
this.material.opacity = opaque ? 1.0 : 1.0 - DESELECT_TRANSPARENCY; this._mesh.material.opacity = opaque ? 1.0 : 1.0 - DESELECT_TRANSPARENCY;
this.material.transparent = !opaque; this._mesh.material.transparent = !opaque;
} }
dispose() { dispose() {
this.geometry.dispose(); this._mesh.geometry.dispose();
} }
updatePoints(shapeData) { updatePoints(shapeData) {
@ -173,7 +232,7 @@ class ShapeMesh extends THREE.Mesh {
throw new Error(`Cannot update object ${this.name}: color is an invalid value.`); throw new Error(`Cannot update object ${this.name}: color is an invalid value.`);
} }
this.material.color.setHex(color); this._mesh.material.color.setHex(color);
this._color = color; this._color = color;
} }
@ -247,10 +306,10 @@ class ShapeMesh extends THREE.Mesh {
this._vertexBuffer.needsUpdate = true; this._vertexBuffer.needsUpdate = true;
this.geometry.boundingBox = null; this._mesh.geometry.boundingBox = null;
this.geometry.boundingSphere = null; this._mesh.geometry.boundingSphere = null;
this.geometry.computeFaceNormals(); this._mesh.geometry.computeFaceNormals();
this.geometry.computeVertexNormals(); this._mesh.geometry.computeVertexNormals();
} }
_updateSide() { _updateSide() {
// TODO use higher precision for export mesh // TODO use higher precision for export mesh
@ -295,8 +354,8 @@ class ShapeMesh extends THREE.Mesh {
const numHeightSteps = this._heightSteps.length; const numHeightSteps = this._heightSteps.length;
this.geometry.dispose(); this._mesh.geometry.dispose();
this.geometry = new THREE.BufferGeometry(); this._mesh.geometry = new THREE.BufferGeometry();
// store total number of indexes and vertices needed // store total number of indexes and vertices needed
let indexBufferLength = 0; let indexBufferLength = 0;
@ -370,7 +429,7 @@ class ShapeMesh extends THREE.Mesh {
const indexes = new Uint32Array(indexBufferLength); const indexes = new Uint32Array(indexBufferLength);
const indexBuffer = new THREE.BufferAttribute(indexes, 1); const indexBuffer = new THREE.BufferAttribute(indexes, 1);
this.geometry.setIndex(indexBuffer); this._mesh.geometry.setIndex(indexBuffer);
let indexCounter = 0; let indexCounter = 0;
for (let i = 0; i < this._shapes.length; i ++) { for (let i = 0; i < this._shapes.length; i ++) {
@ -407,7 +466,7 @@ class ShapeMesh extends THREE.Mesh {
this._vertices = new Float32Array(vertexBufferLength); this._vertices = new Float32Array(vertexBufferLength);
this._vertexBuffer = new THREE.BufferAttribute(this._vertices, 3); this._vertexBuffer = new THREE.BufferAttribute(this._vertices, 3);
this.geometry.addAttribute('position', this._vertexBuffer); this._mesh.geometry.addAttribute('position', this._vertexBuffer);
} }
} }

View File

@ -1,3 +1,4 @@
import { determineActiveShape } from '../shape/shapeDataUtils.js';
import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js'; import { SHAPE_TYPE_PROPERTIES } from '../constants/shapeTypeProperties.js';
import * as THREE from 'three'; import * as THREE from 'three';
import ShapeMesh from './ShapeMesh.js'; import ShapeMesh from './ShapeMesh.js';
@ -45,23 +46,35 @@ export default class ShapesManager extends THREE.Object3D {
} }
} }
for (const id in state.objectsById) { const activeShapes = determineActiveShape(state);
// const shapeData = this._state.objectsById[id];
const ids = Object.keys(state.objectsById).sort((a, b) => {
const solidA = state.objectsById[a].solid;
const solidB = state.objectsById[b].solid;
if (solidA === solidB) return 0;
return solidA ? 1 : -1;
});
const holes = [];
for (let i = 0; i < ids.length; i ++) {
const id = ids[i];
const newShapeData = state.objectsById[id]; const newShapeData = state.objectsById[id];
const active = activeShapes[id];
if (!SHAPE_TYPE_PROPERTIES[newShapeData.type].D3Visible) continue; if (!SHAPE_TYPE_PROPERTIES[newShapeData.type].D3Visible) continue;
// add new shapes // add new shapes
if (!this._state || !this._state.objectsById[id]) { if (!this._state || !this._state.objectsById[id]) {
this._handleShapeAdded(newShapeData); this._handleShapeAdded(newShapeData, holes, active);
render = true; render = true;
} else { } else {
const { mesh } = this._meshes[id]; const { mesh } = this._meshes[id];
if (mesh.update(newShapeData)) { if (mesh.update(newShapeData, holes, active)) {
render = true; render = true;
} }
} }
if (!newShapeData.solid && !active) holes.push(this._meshes[id]);
} }
this._state = state; this._state = state;
return render; return render;
@ -90,10 +103,10 @@ export default class ShapesManager extends THREE.Object3D {
this._spaces[space].remove(mesh); this._spaces[space].remove(mesh);
} }
_handleShapeAdded(shapeData) { _handleShapeAdded(shapeData, holes) {
if (!SHAPE_TYPE_PROPERTIES[shapeData.type].D3Visible) return; if (!SHAPE_TYPE_PROPERTIES[shapeData.type].D3Visible) return;
const { space } = shapeData; const { space } = shapeData;
const mesh = new ShapeMesh(shapeData, this._toonShader); const mesh = new ShapeMesh(shapeData, holes, this._toonShader);
this._meshes[shapeData.UID] = { mesh, space }; this._meshes[shapeData.UID] = { mesh, space };
this._spaces[space].add(mesh); this._spaces[space].add(mesh);

View File

@ -22,6 +22,10 @@ export default function (state, action) {
const fill = fillBool ? contextTools.FILL_TOGGLE_FILL : contextTools.FILL_TOGGLE_OUTLINE; const fill = fillBool ? contextTools.FILL_TOGGLE_FILL : contextTools.FILL_TOGGLE_OUTLINE;
menus = select(menus, fill); menus = select(menus, fill);
const solidBool = firstSelected && state.objectsById[firstSelected.id].solid;
const solid = solidBool ? contextTools.HOLE_TOGGLE_SOLID : contextTools.HOLE_TOGGLE_HOLE;
menus = select(menus, solid);
return update(state, { menus: { $set: menus } }); return update(state, { menus: { $set: menus } });
} }
@ -104,6 +108,18 @@ export default function (state, action) {
}); });
} }
case contextTools.HOLE_TOGGLE_HOLE:
case contextTools.HOLE_TOGGLE_SOLID: {
const solid = action.tool === contextTools.HOLE_TOGGLE_SOLID;
return update(state, {
objectsById: state.selection.objects.reduce((updateObject, { id }) => {
updateObject[id] = { solid: { $set: solid } };
return updateObject;
}, {})
});
}
case contextTools.ALIGN_LEFT: case contextTools.ALIGN_LEFT:
case contextTools.ALIGN_HORIZONTAL: case contextTools.ALIGN_HORIZONTAL:
case contextTools.ALIGN_RIGHT: case contextTools.ALIGN_RIGHT:

View File

@ -69,3 +69,17 @@ function shapeDataToShapeRaw(shapeData) {
return new Shape(shapeData); return new Shape(shapeData);
} }
} }
// TODO can maybe be memoized
export const determineActiveShape = (state) => {
const selectedObjects = state.selection.objects.map(({ id }) => id);
const activeShapes = {};
for (const id in state.objectsById) {
if (state.d2.activeShape === id) {
activeShapes[id] = true;
continue;
}
activeShapes[id] = state.d2.transform.active && selectedObjects.includes(id);
}
return activeShapes;
};

View File

@ -92,13 +92,13 @@ export function generateExportMesh(state, options = {}) {
materials.push(material); materials.push(material);
} }
if (unionGeometry) objectGeometry = new THREE_BSP(objectGeometry, materials.length); if (unionGeometry) objectGeometry = new THREE_BSP(objectGeometry, materialIndex);
if (exportGeometry) { if (exportGeometry) {
if (unionGeometry) { if (unionGeometry) {
exportGeometry = exportGeometry.union(objectGeometry); exportGeometry = exportGeometry.union(objectGeometry);
} else { } else {
exportGeometry = exportGeometry.merge(objectGeometry, undefined, materials.length); exportGeometry.merge(objectGeometry, undefined, materialIndex);
} }
} else { } else {
exportGeometry = objectGeometry; exportGeometry = objectGeometry;