This commit is contained in:
Philip Rosedale 2015-01-21 09:12:15 -08:00
commit ffbe924734
25 changed files with 660 additions and 621 deletions

View file

@ -311,7 +311,7 @@ MetavoxelPersister::MetavoxelPersister(MetavoxelServer* server) :
const char* SAVE_FILE = "/resources/metavoxels.dat";
const int FILE_MAGIC = 0xDADAFACE;
const int FILE_VERSION = 3;
const int FILE_VERSION = 4;
void MetavoxelPersister::load() {
QString path = QCoreApplication::applicationDirPath() + SAVE_FILE;

View file

@ -44,10 +44,16 @@ var entityListTool = EntityListTool();
var hasShownPropertiesTool = false;
var entityListVisible = false;
selectionManager.addEventListener(function() {
selectionDisplay.updateHandles();
if (selectionManager.hasSelection() && !hasShownPropertiesTool) {
// Open properties and model list, but force selection of model list tab
propertiesTool.setVisible(false);
entityListTool.setVisible(false);
propertiesTool.setVisible(true);
entityListTool.setVisible(true);
hasShownPropertiesTool = true;
}
});
@ -690,8 +696,8 @@ function setupModelMenus() {
print("delete exists... don't add ours");
}
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Model List...", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Model List..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", shortcutKey: "CTRL+META+L", afterItem: "Models" });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste Models", shortcutKey: "CTRL+META+V", afterItem: "Entity List..." });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", shortcutKey: "CTRL+META+L",
afterItem: "Paste Models", isCheckable: true, isChecked: true });
Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", shortcutKey: "CTRL+META+S",
@ -703,6 +709,7 @@ function setupModelMenus() {
Menu.addMenuItem({ menuName: "File", menuItemName: "Export Models", shortcutKey: "CTRL+META+E", afterItem: "Models" });
Menu.addMenuItem({ menuName: "File", menuItemName: "Import Models", shortcutKey: "CTRL+META+I", afterItem: "Export Models" });
Menu.addMenuItem({ menuName: "View", menuItemName: MENU_EASE_ON_FOCUS, afterItem: MENU_INSPECT_TOOL_ENABLED,
isCheckable: true, isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) == "true" });
@ -718,7 +725,7 @@ function cleanupModelMenus() {
Menu.removeMenuItem("Edit", "Delete");
}
Menu.removeMenuItem("Edit", "Model List...");
Menu.removeMenuItem("Edit", "Entity List...");
Menu.removeMenuItem("Edit", "Paste Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
@ -755,6 +762,28 @@ Script.update.connect(function (deltaTime) {
selectionDisplay.checkMove();
});
function deleteSelectedEntities() {
if (SelectionManager.hasSelection()) {
print(" Delete Entities");
SelectionManager.saveProperties();
var savedProperties = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID.id];
SelectionManager.savedProperties[entityID.id];
savedProperties.push({
entityID: entityID,
properties: initialProperties
});
Entities.deleteEntity(entityID);
}
SelectionManager.clearSelections();
pushCommandForSelections([], savedProperties);
} else {
print(" Delete Entity.... not holding...");
}
}
function handeMenuEvent(menuItem) {
if (menuItem == "Allow Selecting of Small Models") {
allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
@ -763,57 +792,7 @@ function handeMenuEvent(menuItem) {
} else if (menuItem == "Allow Selecting of Lights") {
Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
} else if (menuItem == "Delete") {
if (SelectionManager.hasSelection()) {
print(" Delete Entities");
SelectionManager.saveProperties();
var savedProperties = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
var entityID = SelectionManager.selections[i];
var initialProperties = SelectionManager.savedProperties[entityID.id];
SelectionManager.savedProperties[entityID.id];
savedProperties.push({
entityID: entityID,
properties: initialProperties
});
Entities.deleteEntity(entityID);
}
SelectionManager.clearSelections();
pushCommandForSelections([], savedProperties);
} else {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Model List...") {
var models = new Array();
models = Entities.findEntities(MyAvatar.position, Number.MAX_VALUE);
for (var i = 0; i < models.length; i++) {
models[i].properties = Entities.getEntityProperties(models[i]);
models[i].toString = function() {
var modelname;
if (this.properties.type == "Model") {
modelname = decodeURIComponent(
this.properties.modelURL.indexOf("/") != -1 ?
this.properties.modelURL.substring(this.properties.modelURL.lastIndexOf("/") + 1) :
this.properties.modelURL);
} else {
modelname = this.properties.id;
}
return "[" + this.properties.type + "] " + modelname;
};
}
var form = [{label: "Model: ", options: models}];
form.push({label: "Action: ", options: ["Properties", "Delete", "Teleport"]});
form.push({ button: "Cancel" });
if (Window.form("Model List", form)) {
var selectedModel = form[0].value;
if (form[1].value == "Properties") {
editModelID = selectedModel;
entityPropertyDialogBox.openDialog(editModelID);
} else if (form[1].value == "Delete") {
Entities.deleteEntity(selectedModel);
} else if (form[1].value == "Teleport") {
MyAvatar.position = selectedModel.properties.position;
}
}
deleteSelectedEntities();
} else if (menuItem == "Paste Models") {
modelImporter.paste();
} else if (menuItem == "Export Models") {
@ -826,6 +805,10 @@ function handeMenuEvent(menuItem) {
}
} else if (menuItem == "Import Models") {
modelImporter.doImport();
} else if (menuItem == "Entity List...") {
if (isActive) {
entityListTool.toggleVisible();
}
}
tooltip.show(false);
}
@ -842,7 +825,7 @@ Controller.keyPressEvent.connect(function(event) {
Controller.keyReleaseEvent.connect(function (event) {
// since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
if (event.text == "BACKSPACE" || event.text == "DELETE") {
handeMenuEvent("Delete");
deleteSelectedEntities();
} else if (event.text == "TAB") {
selectionDisplay.toggleSpaceMode();
} else if (event.text == "f") {

View file

@ -1,14 +1,32 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="list.min.js"></script>
<script>
var entities = {};
var selectedEntities = [];
var currentSortColumn = 'type';
var currentSortOrder = 'asc';
var entityList = null;
var refreshEntityListTimer = null;
var ASC_STRING = '&nbsp;&#x25BE;';
var DESC_STRING = '&nbsp;&#x25B4;';
function loaded() {
entityList = new List('entity-list', { valueNames: ['type', 'url']});
entityList.clear();
elEntityTable = document.getElementById("entity-table");
elEntityTableBody = document.getElementById("entity-table-body");
elRefresh = document.getElementById("refresh");
elDelete = document.getElementById("delete");
elTeleport = document.getElementById("teleport");
document.getElementById("entity-type").onclick = function() {
setSortColumn('type');
};
document.getElementById("entity-url").onclick = function() {
setSortColumn('url');
};
function onRowClicked(e) {
var id = this.dataset.entityId;
@ -20,7 +38,8 @@
selectedEntities = selection;
entities[id].el.className = 'selected';
this.className = 'selected';
EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate",
focus: false,
@ -29,8 +48,6 @@
}
function onRowDoubleClicked() {
var id = this.dataset.entityId;
EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate",
focus: true,
@ -40,41 +57,91 @@
function addEntity(id, type, url) {
if (entities[id] === undefined) {
var el = document.createElement('tr');
el.setAttribute('id', 'entity_' + id);
el.innerHTML += "<td>" + type + "</td>";
el.innerHTML += "<td>" + url + "</td>";
el.dataset.entityId = id;
el.onclick = onRowClicked;
el.ondblclick = onRowDoubleClicked;
elEntityTable.appendChild(el);
var urlParts = url.split('/');
var filename = urlParts[urlParts.length - 1];
// Add element to local dict
entities[id] = {
id: id,
name: id,
el: el,
};
}
}
entityList.add([{ id: id, type: type, url: filename }], function(items) {
var el = items[0].elm;
var id = items[0]._values.id;
entities[id] = {
id: id,
name: id,
el: el,
};
el.setAttribute('id', 'entity_' + id);
el.setAttribute('title', url);
el.dataset.entityId = id;
el.onclick = onRowClicked;
el.ondblclick = onRowDoubleClicked;
el.innerHTML
});
function removeEntity(id) {
if (entities[id] !== undefined) {
elEntityTable.removeChild(entities[id].el);
delete entities[id];
if (refreshEntityListTimer) {
clearTimeout(refreshEntityListTimer);
}
refreshEntityListTimer = setTimeout(refreshEntityListObject, 50);
}
}
function clearEntities() {
for (id in entities) {
elEntityTable.removeChild(entities[id].el);
}
entities = {};
entityList.clear();
}
var elSortOrder = {
type: document.querySelector('#entity-type .sort-order'),
url: document.querySelector('#entity-url .sort-order'),
}
function setSortColumn(column) {
if (currentSortColumn == column) {
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc";
} else {
elSortOrder[currentSortColumn].style.display = 'none';
elSortOrder[column].style.display = 'inline';
currentSortColumn = column;
currentSortOrder = "asc";
}
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASC_STRING : DESC_STRING;
entityList.sort(currentSortColumn, { order: currentSortOrder });
}
function refreshEntities() {
clearEntities();
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
}
function refreshEntityListObject() {
refreshEntityListTimer = null;
entityList.sort(currentSortColumn, { order: currentSortOrder });
entityList.search(document.getElementById("filter").value);
}
function updateSelectedEntities(selectedEntities) {
var notFound = false;
for (var id in entities) {
entities[id].el.className = '';
}
for (var i = 0; i < selectedEntities.length; i++) {
var id = selectedEntities[i];
if (id in entities) {
var entity = entities[id];
entity.el.className = 'selected';
} else {
notFound = true;
}
}
return notFound;
}
elRefresh.onclick = function() {
clearEntities();
EventBridge.emitWebEvent(JSON.stringify({ type: 'refresh' }));
refreshEntities();
}
elTeleport.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
}
elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
refreshEntities();
}
if (window.EventBridge !== undefined) {
@ -82,16 +149,9 @@
data = JSON.parse(data);
if (data.type == "selectionUpdate") {
selectedEntities = data.selectedIDs;
for (var id in entities) {
entities[id].el.className = '';
}
for (var i = 0; i < data.selectedIDs.length; i++) {
var id = data.selectedIDs[i];
if (id in entities) {
var entity = entities[id];
entity.el.className = 'selected';
}
var notFound = updateSelectedEntities(data.selectedIDs);
if (notFound) {
refreshEntities();
}
} else if (data.type == "update") {
var newEntities = data.entities;
@ -99,27 +159,39 @@
var id = newEntities[i].id;
addEntity(id, newEntities[i].type, newEntities[i].url);
}
updateSelectedEntities(data.selectedIDs);
}
});
setTimeout(refreshEntities, 1000);
}
}
</script>
</head>
<body onload='loaded();'>
<div>
<button id="refresh">Refresh</button>
<input type="button" id="refresh" value="Refresh"></button>
<input type="button" id="teleport" value="Teleport"></button>
<input type="button" id="delete" style="background-color: rgb(244, 64, 64); float: right" value="Delete"></button>
</div>
<table id="entity-table">
<thead>
<tr>
<th id="entity-type">Type</th>
<th id="entity-url">URL</th>
</tr>
</thead>
<tbody id="entity-table-body">
</tbody>
</table>
<div id="entity-list">
<input type="text" class="search" id="filter" placeholder="Filter" />
<table id="entity-table">
<thead>
<tr>
<th id="entity-type" data-sort="type">Type <span class="sort-order" style="display: inline">&nbsp;&#x25BE;</span></th>
<th id="entity-url" data-sort="url">URL <span class="sort-order" style="display: none">&nbsp;&#x25BE;</span></th>
</tr>
</thead>
<tbody class="list" id="entity-table-body">
<tr>
<td class="id" style="display: none">Type</td>
<td class="type">Type</td>
<td class="url"><div class='outer'><div class='inner'>URL</div></div></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

1
examples/html/list.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -5,9 +5,10 @@ body {
margin: 0;
padding: 0;
background-color: #efefef;
background-color: rgb(76, 76, 76);
color: rgb(204, 204, 204);
font-family: Arial;
font-size: 11.5px;
font-size: 11px;
-webkit-touch-callout: none;
-webkit-user-select: none;
@ -17,12 +18,6 @@ body {
user-select: none;
}
body.properties {
background-color: rgb(76, 76, 76);
color: rgb(204, 204, 204);
font-size: 11px;
}
.selectable {
-webkit-touch-callout: text;
-webkit-user-select: text;
@ -117,12 +112,13 @@ input.coord {
table#entity-table {
border-collapse: collapse;
font-family: Sans-Serif;
/* font-size: 12px; */
font-size: 10px;
width: 100%;
}
#entity-table tr {
cursor: pointer;
border-bottom: 1px solid rgb(63, 63, 63)
}
#entity-table tr.selected {
@ -139,17 +135,22 @@ table#entity-table {
}
#entity-table td {
font-size: 11px;
border: 0px black solid;
word-wrap: nowrap;
white-space: nowrap;
text-overflow: ellipsis;
}
#entity-table td.url {
white-space: nowrap;
overflow: hidden;
}
th#entity-type {
width: 60px;
}
th#entity-url {
}
div.input-area {
@ -226,3 +227,20 @@ table#properties-list {
col#col-label {
width: 130px;
}
div.outer {
position: relative;
}
div.inner {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
position: absolute;
width: 100%;
}
td {
vertical-align: top;
}

View file

@ -13,6 +13,10 @@ EntityListTool = function(opts) {
webView.setVisible(visible);
};
that.toggleVisible = function() {
that.setVisible(!visible);
}
selectionManager.addEventListener(function() {
var selectedIDs = [];
@ -24,10 +28,38 @@ EntityListTool = function(opts) {
type: 'selectionUpdate',
selectedIDs: selectedIDs,
};
print("Sending: " + JSON.stringify(data));
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
});
function sendUpdate() {
var entities = [];
var ids = Entities.findEntities(MyAvatar.position, 100);
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var properties = Entities.getEntityProperties(id);
entities.push({
id: id.id,
type: properties.type,
url: properties.type == "Model" ? properties.modelURL : "",
});
}
var selectedIDs = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
selectedIDs.push(selectionManager.selections[i].id);
}
var data = {
type: "update",
entities: entities,
selectedIDs: selectedIDs,
};
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
}
webView.eventBridge.webEventReceived.connect(function(data) {
print("Got: " + data);
data = JSON.parse(data);
if (data.type == "selectionUpdate") {
var ids = data.entityIds;
@ -46,22 +78,13 @@ EntityListTool = function(opts) {
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
} else if (data.type == "refresh") {
var entities = [];
var ids = Entities.findEntities(MyAvatar.position, 100);
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var properties = Entities.getEntityProperties(id);
entities.push({
id: id.id,
type: properties.type,
url: properties.type == "Model" ? properties.modelURL : "",
});
sendUpdate();
} else if (data.type == "teleport") {
if (selectionManager.hasSelection()) {
MyAvatar.position = selectionManager.worldPosition;
}
var data = {
type: "update",
entities: entities,
};
webView.eventBridge.emitScriptEvent(JSON.stringify(data));
} else if (data.type == "delete") {
deleteSelectedEntities();
}
});

View file

@ -43,5 +43,5 @@ void main(void) {
gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0));
// pass along the scaled/offset texture coordinates
gl_TexCoord[0] = vec4((heightCoord - heightScale.st) * colorScale, 0.0, 1.0);
gl_TexCoord[0] = vec4((heightCoord - vec2(0.5, 0.5)) * colorScale + vec2(0.5, 0.5), 0.0, 1.0);
}

View file

@ -22,7 +22,8 @@ varying vec4 alphaValues;
void main(void) {
// blend the splat textures
gl_FragColor = gl_Color * (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x +
gl_FragColor = vec4(gl_Color.rgb, step(1.0, gl_Color.a + 1.0 / 512.0)) *
(texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x +
texture2D(diffuseMaps[1], gl_TexCoord[1].st) * alphaValues.y +
texture2D(diffuseMaps[2], gl_TexCoord[2].st) * alphaValues.z +
texture2D(diffuseMaps[3], gl_TexCoord[3].st) * alphaValues.w);

View file

@ -58,7 +58,7 @@ void main(void) {
gl_TexCoord[3] = textureSpacePosition * vec4(splatTextureScalesS[3], splatTextureScalesT[3], 0.0, 1.0);
// compute the alpha values for each texture
float value = texture2D(textureMap, (gl_MultiTexCoord0.st - heightScale) * textureScale).r;
float value = texture2D(textureMap, (gl_MultiTexCoord0.st - vec2(0.5, 0.5)) * textureScale + vec2(0.5, 0.5)).r;
vec4 valueVector = vec4(value, value, value, value);
alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima);
}

View file

@ -1425,8 +1425,18 @@ public:
char material;
void setColorMaterial(const StackArray::Entry& entry) { color = entry.color; material = entry.material; }
void mix(const EdgeCrossing& first, const EdgeCrossing& second, float t);
};
void EdgeCrossing::mix(const EdgeCrossing& first, const EdgeCrossing& second, float t) {
point = glm::mix(first.point, second.point, t);
normal = glm::normalize(glm::mix(first.normal, second.normal, t));
color = qRgb(glm::mix(qRed(first.color), qRed(second.color), t), glm::mix(qGreen(first.color), qGreen(second.color), t),
glm::mix(qBlue(first.color), qBlue(second.color), t));
material = (t < 0.5f) ? first.material : second.material;
}
const int MAX_NORMALS_PER_VERTEX = 4;
class NormalIndex {
@ -1480,7 +1490,6 @@ public:
void swap(IndexVector& other) { QVector<NormalIndex>::swap(other); qSwap(position, other.position); }
const NormalIndex& get(int y) const;
const NormalIndex& getClosest(int y) const;
};
const NormalIndex& IndexVector::get(int y) const {
@ -1489,56 +1498,6 @@ const NormalIndex& IndexVector::get(int y) const {
return (relative >= 0 && relative < size()) ? at(relative) : invalidIndex;
}
const NormalIndex& IndexVector::getClosest(int y) const {
static NormalIndex invalidIndex = { { -1, -1, -1, -1 } };
int relative = y - position;
if (relative < 0 || relative >= size()) {
return invalidIndex;
}
const NormalIndex& first = at(relative);
if (first.isValid()) {
return first;
}
for (int distance = 1; relative - distance >= 0 || relative + distance < size(); distance++) {
int previous = relative - distance;
if (previous >= 0) {
const NormalIndex& previousIndex = at(previous);
if (previousIndex.isValid()) {
return previousIndex;
}
}
int next = relative + distance;
if (next < size()) {
const NormalIndex& nextIndex = at(next);
if (nextIndex.isValid()) {
return nextIndex;
}
}
}
return invalidIndex;
}
static inline void appendIndices(QVector<int>& indices, QMultiHash<VoxelCoord, int>& quadIndices,
const QVector<VoxelPoint>& vertices, float step, int i0, int i1, int i2, int i3) {
int newIndices[] = { i0, i1, i2, i3 };
glm::vec3 minima(FLT_MAX, FLT_MAX, FLT_MAX), maxima(-FLT_MAX, -FLT_MAX, -FLT_MAX);
int indexIndex = indices.size();
for (unsigned int i = 0; i < sizeof(newIndices) / sizeof(newIndices[0]); i++) {
int index = newIndices[i];
indices.append(index);
const glm::vec3& vertex = vertices.at(index).vertex;
minima = glm::min(vertex, minima);
maxima = glm::max(vertex, maxima);
}
for (int z = (int)minima.z, endZ = (int)glm::ceil(maxima.z); z < endZ; z++) {
for (int y = (int)minima.x, endY = (int)glm::ceil(maxima.y); y < endY; y++) {
for (int x = (int)minima.x, endX = (int)glm::ceil(maxima.x); x < endX; x++) {
quadIndices.insert(qRgb(x, y, z), indexIndex);
}
}
}
}
void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const glm::vec3& translation,
const glm::quat& rotation, const glm::vec3& scale, bool cursor) {
if (!node->getHeight()) {
@ -1617,7 +1576,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
glGenTextures(1, &_colorTextureID);
glBindTexture(GL_TEXTURE_2D, _colorTextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (node->getColor()) {
@ -1625,6 +1584,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, node->getColor()->getWidth(),
contents.size() / (node->getColor()->getWidth() * DataBlock::COLOR_BYTES),
0, GL_RGB, GL_UNSIGNED_BYTE, contents.constData());
glGenerateMipmap(GL_TEXTURE_2D);
} else {
const quint8 WHITE_COLOR[] = { 255, 255, 255 };
@ -1705,7 +1665,7 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
}
const int EDGES_PER_CUBE = 12;
EdgeCrossing crossings[EDGES_PER_CUBE];
EdgeCrossing crossings[EDGES_PER_CUBE * 2];
// as we scan down the cube generating vertices between grid points, we remember the indices of the last
// (element, line, section--x, y, z) so that we can connect generated vertices as quads
@ -1736,13 +1696,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
lineSrc[stackWidth].getExtents(minimumY, maximumY);
}
if (maximumY >= minimumY) {
int position = minimumY;
int count = maximumY - minimumY + 1;
NormalIndex lastIndexY = { { -1, -1, -1, -1 } };
indicesX.position = position;
indicesX.resize(count);
indicesZ[x].position = position;
indicesZ[x].resize(count);
float heightfieldHeight = *heightLineSrc * voxelScale;
float nextHeightfieldHeightX = heightLineSrc[1] * voxelScale;
float nextHeightfieldHeightZ = heightLineSrc[width] * voxelScale;
@ -1753,6 +1706,8 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
const int LOWER_RIGHT_CORNER = 8;
const int NO_CORNERS = 0;
const int ALL_CORNERS = UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER;
const int CORNER_COUNT = 4;
const int NEXT_CORNERS[] = { 1, 3, 0, 2 };
int corners = NO_CORNERS;
if (heightfieldHeight != 0.0f) {
corners |= UPPER_LEFT_CORNER;
@ -1767,37 +1722,38 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
corners |= LOWER_RIGHT_CORNER;
}
bool stitchable = x != 0 && z != 0 && !(corners == NO_CORNERS || corners == ALL_CORNERS);
VoxelPoint cornerPoints[4];
EdgeCrossing cornerCrossings[CORNER_COUNT];
int clampedX = qMax(x - 1, 0), clampedZ = qMax(z - 1, 0);
int cornerMinimumY = INT_MAX, cornerMaximumY = -1;
if (stitchable) {
for (unsigned int i = 0; i < sizeof(cornerPoints) / sizeof(cornerPoints[0]); i++) {
for (int i = 0; i < CORNER_COUNT; i++) {
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
VoxelPoint& point = cornerPoints[i];
int clampedOffsetX = clampedX + offsetX, clampedOffsetZ = clampedZ + offsetZ;
point.vertex = glm::vec3(clampedOffsetX, *height * voxelScale, clampedOffsetZ) * step;
float heightValue = *height * voxelScale;
int y = (int)heightValue;
cornerMinimumY = qMin(cornerMinimumY, y);
cornerMaximumY = qMax(cornerMaximumY, y);
EdgeCrossing& crossing = cornerCrossings[i];
crossing.point = glm::vec3(offsetX, heightValue, offsetZ);
int left = height[-1];
int right = height[1];
int down = height[-width];
int up = height[width];
glm::vec3 normal = glm::normalize(glm::vec3((left == 0 || right == 0) ? 0.0f : left - right,
crossing.normal = glm::normalize(glm::vec3((left == 0 || right == 0) ? 0.0f : left - right,
2.0f / voxelScale, (up == 0 || down == 0) ? 0.0f : down - up));
point.normal[0] = normal.x * numeric_limits<qint8>::max();
point.normal[1] = normal.y * numeric_limits<qint8>::max();
point.normal[2] = normal.z * numeric_limits<qint8>::max();
int clampedOffsetX = clampedX + offsetX, clampedOffsetZ = clampedZ + offsetZ;
if (colorSrc) {
const uchar* color = colorSrc + ((int)(clampedOffsetZ * colorStepZ) * colorWidth +
(int)(clampedOffsetX * colorStepX)) * DataBlock::COLOR_BYTES;
point.color[0] = color[0];
point.color[1] = color[1];
point.color[2] = color[2];
crossing.color = qRgb(color[0], color[1], color[2]);
} else {
point.color[0] = point.color[1] = point.color[2] = numeric_limits<quint8>::max();
crossing.color = qRgb(numeric_limits<quint8>::max(), numeric_limits<quint8>::max(),
numeric_limits<quint8>::max());
}
int material = 0;
if (materialSrc) {
@ -1812,12 +1768,18 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
material = mapping;
}
}
point.materials[0] = material;
point.materials[1] = point.materials[2] = point.materials[3] = 0;
point.materialWeights[0] = numeric_limits<quint8>::max();
point.materialWeights[1] = point.materialWeights[2] = point.materialWeights[3] = 0;
crossing.material = material;
}
minimumY = qMin(minimumY, cornerMinimumY);
maximumY = qMax(maximumY, cornerMaximumY);
}
int position = minimumY;
int count = maximumY - minimumY + 1;
NormalIndex lastIndexY = { { -1, -1, -1, -1 } };
indicesX.position = position;
indicesX.resize(count);
indicesZ[x].position = position;
indicesZ[x].resize(count);
for (int y = position, end = position + count; y < end; y++) {
const StackArray::Entry& entry = lineSrc->getEntry(y, heightfieldHeight);
if (displayHermite && x != 0 && z != 0 && !lineSrc->isEmpty() && y >= lineSrc->getPosition()) {
@ -1875,91 +1837,179 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
if (alphaTotal == 0 || alphaTotal == possibleTotal) {
continue; // no corners set/all corners set
}
// we first look for crossings with the heightfield corner vertices; these take priority
int crossingCount = 0;
if (y >= cornerMinimumY && y <= cornerMaximumY) {
// first look for set corners, which override any interpolated values
int crossedCorners = NO_CORNERS;
for (int i = 0; i < CORNER_COUNT; i++) {
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
float heightValue = *height * voxelScale;
if (heightValue >= y && heightValue < y + 1) {
crossedCorners |= (1 << i);
}
}
switch (crossedCorners) {
case UPPER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | LOWER_RIGHT_CORNER:
crossings[crossingCount++] = cornerCrossings[0];
crossings[crossingCount - 1].point.y -= y;
break;
case UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER:
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER:
crossings[crossingCount++] = cornerCrossings[1];
crossings[crossingCount - 1].point.y -= y;
break;
case LOWER_LEFT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER | UPPER_LEFT_CORNER:
crossings[crossingCount++] = cornerCrossings[2];
crossings[crossingCount - 1].point.y -= y;
break;
case LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER:
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER:
crossings[crossingCount++] = cornerCrossings[3];
crossings[crossingCount - 1].point.y -= y;
break;
case NO_CORNERS:
for (int i = 0; i < CORNER_COUNT; i++) {
if (!(corners & (1 << i))) {
continue;
}
int offsetX = (i & X_MAXIMUM_FLAG) ? 1 : 0;
int offsetZ = (i & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* height = heightLineSrc + offsetZ * width + offsetX;
float heightValue = *height * voxelScale;
int nextIndex = NEXT_CORNERS[i];
if (!(corners & (1 << nextIndex))) {
continue;
}
int nextOffsetX = (nextIndex & X_MAXIMUM_FLAG) ? 1 : 0;
int nextOffsetZ = (nextIndex & Y_MAXIMUM_FLAG) ? 1 : 0;
const quint16* nextHeight = heightLineSrc + nextOffsetZ * width + nextOffsetX;
float nextHeightValue = *nextHeight * voxelScale;
float divisor = (nextHeightValue - heightValue);
if (divisor == 0.0f) {
continue;
}
float t1 = (y - heightValue) / divisor;
float t2 = (y + 1 - heightValue) / divisor;
if (t1 >= 0.0f && t1 <= 1.0f) {
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t1);
crossings[crossingCount - 1].point.y -= y;
}
if (t2 >= 0.0f && t2 <= 1.0f) {
crossings[crossingCount++].mix(cornerCrossings[i], cornerCrossings[nextIndex], t2);
crossings[crossingCount - 1].point.y -= y;
}
}
break;
}
}
// the terrifying conditional code that follows checks each cube edge for a crossing, gathering
// its properties (color, material, normal) if one is present; as before, boundary edges are excluded
int crossingCount = 0;
const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight);
if (middleX) {
const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX);
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
if (alpha0 != alpha1) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
crossing.setColorMaterial(alpha0 == 0 ? nextEntryX : entry);
if (crossingCount == 0) {
const StackArray::Entry& nextEntryY = lineSrc->getEntry(y + 1, heightfieldHeight);
if (middleX) {
const StackArray::Entry& nextEntryX = lineSrc[1].getEntry(y, nextHeightfieldHeightX);
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
if (alpha0 != alpha1) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(entry.getHermiteX(crossing.normal), 0.0f, 0.0f);
crossing.setColorMaterial(alpha0 == 0 ? nextEntryX : entry);
}
if (alpha1 != alpha3) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(1.0f, nextEntryX.getHermiteY(crossing.normal), 0.0f);
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXY : nextEntryX);
}
if (alpha2 != alpha3) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(nextEntryY.getHermiteX(crossing.normal), 1.0f, 0.0f);
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y,
nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
const StackArray::Entry& nextEntryXYZ = lineSrc[stackWidth + 1].getEntry(
y + 1, nextHeightfieldHeightXZ);
if (alpha1 != alpha5) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXZ : nextEntryX);
}
if (alpha3 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1,
nextHeightfieldHeightX);
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
}
if (alpha4 != alpha5) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(nextEntryZ.getHermiteX(crossing.normal), 0.0f, 1.0f);
crossing.setColorMaterial(alpha4 == 0 ? nextEntryXZ : nextEntryZ);
}
if (alpha5 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f);
crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ);
}
if (alpha6 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(
y + 1, nextHeightfieldHeightZ);
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
}
}
}
if (alpha1 != alpha3) {
if (alpha0 != alpha2) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(1.0f, nextEntryX.getHermiteY(crossing.normal), 0.0f);
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXY : nextEntryX);
}
if (alpha2 != alpha3) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(nextEntryY.getHermiteX(crossing.normal), 1.0f, 0.0f);
crossing.setColorMaterial(alpha2 == 0 ? nextEntryXY : nextEntryY);
crossing.point = glm::vec3(0.0f, entry.getHermiteY(crossing.normal), 0.0f);
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
const StackArray::Entry& nextEntryXYZ = lineSrc[stackWidth + 1].getEntry(
y + 1, nextHeightfieldHeightXZ);
if (alpha1 != alpha5) {
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(y + 1,
nextHeightfieldHeightZ);
if (alpha0 != alpha4) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(1.0f, 0.0f, nextEntryX.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha1 == 0 ? nextEntryXZ : nextEntryX);
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha0 == 0 ? nextEntryZ : entry);
}
if (alpha3 != alpha7) {
if (alpha2 != alpha6) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXY = lineSrc[1].getEntry(y + 1, nextHeightfieldHeightX);
crossing.point = glm::vec3(1.0f, 1.0f, nextEntryXY.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha3 == 0 ? nextEntryXYZ : nextEntryXY);
crossing.point = glm::vec3(0.0f, 1.0f, nextEntryY.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha2 == 0 ? nextEntryYZ : nextEntryY);
}
if (alpha4 != alpha5) {
if (alpha4 != alpha6) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(nextEntryZ.getHermiteX(crossing.normal), 0.0f, 1.0f);
crossing.setColorMaterial(alpha4 == 0 ? nextEntryXZ : nextEntryZ);
}
if (alpha5 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryXZ = lineSrc[stackWidth + 1].getEntry(
y, nextHeightfieldHeightXZ);
crossing.point = glm::vec3(1.0f, nextEntryXZ.getHermiteY(crossing.normal), 1.0f);
crossing.setColorMaterial(alpha5 == 0 ? nextEntryXYZ : nextEntryXZ);
}
if (alpha6 != alpha7) {
EdgeCrossing& crossing = crossings[crossingCount++];
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(
y + 1, nextHeightfieldHeightZ);
crossing.point = glm::vec3(nextEntryYZ.getHermiteX(crossing.normal), 1.0f, 1.0f);
crossing.setColorMaterial(alpha6 == 0 ? nextEntryXYZ : nextEntryYZ);
crossing.point = glm::vec3(0.0f, nextEntryZ.getHermiteY(crossing.normal), 1.0f);
crossing.setColorMaterial(alpha4 == 0 ? nextEntryYZ : nextEntryZ);
}
}
}
if (alpha0 != alpha2) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(0.0f, entry.getHermiteY(crossing.normal), 0.0f);
crossing.setColorMaterial(alpha0 == 0 ? nextEntryY : entry);
}
if (middleZ) {
const StackArray::Entry& nextEntryZ = lineSrc[stackWidth].getEntry(y, nextHeightfieldHeightZ);
const StackArray::Entry& nextEntryYZ = lineSrc[stackWidth].getEntry(y + 1, nextHeightfieldHeightZ);
if (alpha0 != alpha4) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(0.0f, 0.0f, entry.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha0 == 0 ? nextEntryZ : entry);
}
if (alpha2 != alpha6) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(0.0f, 1.0f, nextEntryY.getHermiteZ(crossing.normal));
crossing.setColorMaterial(alpha2 == 0 ? nextEntryYZ : nextEntryY);
}
if (alpha4 != alpha6) {
EdgeCrossing& crossing = crossings[crossingCount++];
crossing.point = glm::vec3(0.0f, nextEntryZ.getHermiteY(crossing.normal), 1.0f);
crossing.setColorMaterial(alpha4 == 0 ? nextEntryYZ : nextEntryZ);
}
}
// determine whether we should ignore this vertex because it will be stitched
// make sure we have valid crossings to include
int validCrossings = 0;
for (int i = 0; i < crossingCount; i++) {
if (qAlpha(crossings[i].color) != 0) {
@ -2107,173 +2157,6 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
point.setNormal(normals[i]);
vertices.append(point);
}
if (stitchable) {
int nextIndex = vertices.size();
const NormalIndex& previousIndexX = lastIndicesX.getClosest(y);
const NormalIndex& previousIndexZ = lastIndicesZ[x].getClosest(y);
switch (corners) {
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER: {
vertices.append(cornerPoints[0]);
vertices.append(cornerPoints[3]);
glm::vec3 normal = glm::cross(cornerPoints[0].vertex - cornerPoints[1].vertex,
cornerPoints[3].vertex - cornerPoints[1].vertex);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex, nextIndex);
if (previousIndexX.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
nextIndex, previousIndexX.getClosestIndex(normal, vertices));
}
break;
}
case UPPER_LEFT_CORNER | LOWER_LEFT_CORNER | LOWER_RIGHT_CORNER: {
vertices.append(cornerPoints[0]);
vertices.append(cornerPoints[3]);
glm::vec3 normal = glm::cross(cornerPoints[3].vertex - cornerPoints[2].vertex,
cornerPoints[0].vertex - cornerPoints[2].vertex);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex, nextIndex + 1, nextIndex + 1);
if (previousIndexZ.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
previousIndexZ.getClosestIndex(normal, vertices), nextIndex);
}
break;
}
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER: {
vertices.append(cornerPoints[1]);
vertices.append(cornerPoints[2]);
vertices.append(cornerPoints[3]);
glm::vec3 normal = glm::cross(cornerPoints[3].vertex - cornerPoints[2].vertex,
cornerPoints[1].vertex - cornerPoints[2].vertex);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 2, nextIndex, nextIndex);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex + 2, nextIndex + 2);
if (previousIndexX.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
previousIndexX.getClosestIndex(normal, vertices), nextIndex + 1);
}
if (previousIndexZ.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
nextIndex, previousIndexZ.getClosestIndex(normal, vertices));
}
break;
}
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER | LOWER_LEFT_CORNER: {
vertices.append(cornerPoints[0]);
vertices.append(cornerPoints[1]);
vertices.append(cornerPoints[2]);
glm::vec3 normal = glm::cross(cornerPoints[2].vertex - cornerPoints[0].vertex,
cornerPoints[1].vertex - cornerPoints[0].vertex);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex, nextIndex);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex, nextIndex + 2, nextIndex + 2);
break;
}
case UPPER_LEFT_CORNER | UPPER_RIGHT_CORNER: {
vertices.append(cornerPoints[0]);
vertices.append(cornerPoints[1]);
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(cornerPoints[1].vertex - first,
cornerPoints[0].vertex - first);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex, nextIndex);
if (previousIndexX.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
nextIndex, previousIndexX.getClosestIndex(normal, vertices));
}
break;
}
case UPPER_RIGHT_CORNER | LOWER_RIGHT_CORNER: {
vertices.append(cornerPoints[1]);
vertices.append(cornerPoints[3]);
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(cornerPoints[3].vertex - first,
cornerPoints[1].vertex - first);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex, nextIndex);
if (previousIndexZ.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex,
firstIndex, nextIndex, previousIndexZ.getClosestIndex(normal, vertices));
}
break;
}
case LOWER_RIGHT_CORNER | LOWER_LEFT_CORNER: {
vertices.append(cornerPoints[3]);
vertices.append(cornerPoints[2]);
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(cornerPoints[2].vertex - first,
cornerPoints[3].vertex - first);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex, nextIndex);
if (previousIndexX.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
previousIndexX.getClosestIndex(normal, vertices), nextIndex + 1);
}
break;
}
case LOWER_LEFT_CORNER | UPPER_LEFT_CORNER: {
vertices.append(cornerPoints[2]);
vertices.append(cornerPoints[0]);
const glm::vec3& first = vertices.at(index.indices[0]).vertex;
glm::vec3 normal = glm::cross(cornerPoints[0].vertex - first,
cornerPoints[2].vertex - first);
int firstIndex = index.getClosestIndex(normal, vertices);
appendIndices(indices, quadIndices, vertices, step, firstIndex,
nextIndex + 1, nextIndex, nextIndex);
if (previousIndexZ.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
previousIndexZ.getClosestIndex(normal, vertices), nextIndex + 1);
}
break;
}
case UPPER_LEFT_CORNER: {
vertices.append(cornerPoints[0]);
glm::vec3 normal = glm::cross(cornerPoints[0].vertex -
vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f));
int firstIndex = index.getClosestIndex(normal, vertices);
if (previousIndexX.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
nextIndex, previousIndexX.getClosestIndex(normal, vertices));
}
if (previousIndexZ.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
previousIndexZ.getClosestIndex(normal, vertices), nextIndex);
}
break;
}
case UPPER_RIGHT_CORNER: {
vertices.append(cornerPoints[1]);
glm::vec3 normal = glm::cross(cornerPoints[1].vertex -
vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f));
int firstIndex = index.getClosestIndex(normal, vertices);
if (previousIndexZ.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
nextIndex, previousIndexZ.getClosestIndex(normal, vertices));
}
break;
}
case LOWER_LEFT_CORNER: {
vertices.append(cornerPoints[2]);
glm::vec3 normal = glm::cross(cornerPoints[2].vertex -
vertices.at(index.indices[0]).vertex, glm::vec3(1.0f, 0.0f, 0.0f));
int firstIndex = index.getClosestIndex(normal, vertices);
if (previousIndexX.isValid()) {
appendIndices(indices, quadIndices, vertices, step, firstIndex, firstIndex,
previousIndexX.getClosestIndex(normal, vertices), nextIndex);
}
break;
}
}
}
}
// the first x, y, and z are repeated for the boundary edge; past that, we consider generating
@ -2407,7 +2290,14 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
baseBatch.heightTextureID = _heightTextureID;
baseBatch.heightScale = glm::vec4(1.0f / width, 1.0f / height, (innerWidth - 1) / -2.0f, (innerHeight - 1) / -2.0f);
baseBatch.colorTextureID = _colorTextureID;
baseBatch.colorScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight);
float widthMultiplier = 1.0f / (0.5f - 1.5f / width);
float heightMultiplier = 1.0f / (0.5f - 1.5f / height);
if (node->getColor()) {
int colorWidth = node->getColor()->getWidth();
int colorHeight = node->getColor()->getContents().size() / (colorWidth * DataBlock::COLOR_BYTES);
baseBatch.colorScale = glm::vec2((0.5f - 0.5f / colorWidth) * widthMultiplier,
(0.5f - 0.5f / colorHeight) * heightMultiplier);
}
Application::getInstance()->getMetavoxels()->addHeightfieldBaseBatch(baseBatch);
if (!(cursor || _networkTextures.isEmpty())) {
@ -2422,7 +2312,12 @@ void HeightfieldNodeRenderer::render(const HeightfieldNodePointer& node, const g
splatBatch.heightTextureID = _heightTextureID;
splatBatch.heightScale = glm::vec4(1.0f / width, 1.0f / height, 0.0f, 0.0f);
splatBatch.materialTextureID = _materialTextureID;
splatBatch.textureScale = glm::vec2((float)width / innerWidth, (float)height / innerHeight);
if (node->getMaterial()) {
int materialWidth = node->getMaterial()->getWidth();
int materialHeight = node->getMaterial()->getContents().size() / materialWidth;
splatBatch.textureScale = glm::vec2((0.5f - 0.5f / materialWidth) * widthMultiplier,
(0.5f - 0.5f / materialHeight) * heightMultiplier);
}
splatBatch.splatTextureOffset = glm::vec2(
glm::dot(translation, rotation * glm::vec3(1.0f, 0.0f, 0.0f)) / scale.x,
glm::dot(translation, rotation * glm::vec3(0.0f, 0.0f, 1.0f)) / scale.z);

View file

@ -41,19 +41,27 @@ WebWindowClass::WebWindowClass(const QString& title, const QString& url, int wid
_dockWidget = new QDockWidget(title, toolWindow);
_dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
QWebView* webView = new QWebView(_dockWidget);
webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge);
webView->setUrl(url);
_dockWidget->setWidget(webView);
_webView = new QWebView(_dockWidget);
_webView->setUrl(url);
addEventBridgeToWindowObject();
_dockWidget->setWidget(_webView);
toolWindow->addDockWidget(Qt::RightDockWidgetArea, _dockWidget);
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
this, &WebWindowClass::addEventBridgeToWindowObject);
connect(this, &WebWindowClass::destroyed, _dockWidget, &QWidget::deleteLater);
}
WebWindowClass::~WebWindowClass() {
}
void WebWindowClass::addEventBridgeToWindowObject() {
_webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge);
}
void WebWindowClass::setVisible(bool visible) {
if (visible) {
QMetaObject::invokeMethod(

View file

@ -14,6 +14,7 @@
#include <QScriptContext>
#include <QScriptEngine>
#include <QWebView>
class ScriptEventBridge : public QObject {
Q_OBJECT
@ -42,9 +43,11 @@ public:
public slots:
void setVisible(bool visible);
ScriptEventBridge* getEventBridge() const { return _eventBridge; }
void addEventBridgeToWindowObject();
private:
QDockWidget* _dockWidget;
QWebView* _webView;
ScriptEventBridge* _eventBridge;
};

View file

@ -220,7 +220,12 @@ unsigned int Overlays::cloneOverlay(unsigned int id) {
} else if (_overlaysWorld.contains(id)) {
thisOverlay = _overlaysWorld[id];
}
return addOverlay(thisOverlay->createClone());
if (thisOverlay) {
return addOverlay(thisOverlay->createClone());
}
return 0; // Not found
}
bool Overlays::editOverlay(unsigned int id, const QScriptValue& properties) {

View file

@ -2432,12 +2432,13 @@ void MappedObjectStreamer::writeRawDelta(Bitstream& out, const QObject* object,
}
QObject* MappedObjectStreamer::read(Bitstream& in, QObject* object) const {
bool reread = (object != NULL);
if (!object && _metaObject) {
object = _metaObject->newInstance();
}
foreach (const StreamerPropertyPair& property, _properties) {
QVariant value = property.first->read(in);
if (property.second.isValid() && object) {
if (property.second.isValid() && object && !reread) {
property.second.write(object, value);
}
}
@ -2445,6 +2446,7 @@ QObject* MappedObjectStreamer::read(Bitstream& in, QObject* object) const {
}
QObject* MappedObjectStreamer::readRawDelta(Bitstream& in, const QObject* reference, QObject* object) const {
bool reread = (object != NULL);
if (!object && _metaObject) {
object = _metaObject->newInstance();
}
@ -2452,7 +2454,7 @@ QObject* MappedObjectStreamer::readRawDelta(Bitstream& in, const QObject* refere
QVariant value;
property.first->readDelta(in, value, (property.second.isValid() && reference &&
reference->metaObject() == _metaObject) ? property.second.read(reference) : QVariant());
if (property.second.isValid() && object) {
if (property.second.isValid() && object && !reread) {
property.second.write(object, value);
}
}
@ -2475,13 +2477,13 @@ void SharedObjectStreamer::writeRawDelta(Bitstream& out, const QObject* object,
QObject* SharedObjectStreamer::read(Bitstream& in, QObject* object) const {
QObject* result = MappedObjectStreamer::read(in, object);
static_cast<SharedObject*>(result)->readExtra(in);
static_cast<SharedObject*>(result)->readExtra(in, object != NULL);
return result;
}
QObject* SharedObjectStreamer::readRawDelta(Bitstream& in, const QObject* reference, QObject* object) const {
QObject* result = MappedObjectStreamer::readRawDelta(in, reference, object);
static_cast<SharedObject*>(result)->readExtraDelta(in, static_cast<const SharedObject*>(reference));
static_cast<SharedObject*>(result)->readExtraDelta(in, static_cast<const SharedObject*>(reference), object != NULL);
return result;
}
@ -2592,6 +2594,7 @@ void GenericObjectStreamer::writeRawDelta(Bitstream& out, const QObject* object,
}
QObject* GenericObjectStreamer::read(Bitstream& in, QObject* object) const {
bool reread = (object != NULL);
if (!object) {
object = new GenericSharedObject(_weakSelf);
}
@ -2599,11 +2602,14 @@ QObject* GenericObjectStreamer::read(Bitstream& in, QObject* object) const {
foreach (const StreamerNamePair& property, _properties) {
values.append(property.first->read(in));
}
static_cast<GenericSharedObject*>(object)->setValues(values);
if (!reread) {
static_cast<GenericSharedObject*>(object)->setValues(values);
}
return object;
}
QObject* GenericObjectStreamer::readRawDelta(Bitstream& in, const QObject* reference, QObject* object) const {
bool reread = (object != NULL);
if (!object) {
object = new GenericSharedObject(_weakSelf);
}
@ -2615,7 +2621,9 @@ QObject* GenericObjectStreamer::readRawDelta(Bitstream& in, const QObject* refer
static_cast<const GenericSharedObject*>(reference)->getValues().at(i) : QVariant());
values.append(value);
}
static_cast<GenericSharedObject*>(object)->setValues(values);
if (!reread) {
static_cast<GenericSharedObject*>(object)->setValues(values);
}
return object;
}

View file

@ -135,7 +135,7 @@ void SharedObject::writeExtra(Bitstream& out) const {
// nothing by default
}
void SharedObject::readExtra(Bitstream& in) {
void SharedObject::readExtra(Bitstream& in, bool reread) {
// nothing by default
}
@ -143,7 +143,7 @@ void SharedObject::writeExtraDelta(Bitstream& out, const SharedObject* reference
// nothing by default
}
void SharedObject::readExtraDelta(Bitstream& in, const SharedObject* reference) {
void SharedObject::readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread) {
// nothing by default
}

View file

@ -84,13 +84,15 @@ public:
virtual void writeExtra(Bitstream& out) const;
/// Reads the non-property contents of this object from the specified stream.
virtual void readExtra(Bitstream& in);
/// \param reread if true, reread the contents from the stream but don't reapply them
virtual void readExtra(Bitstream& in, bool reread = false);
/// Writes the delta-encoded non-property contents of this object to the specified stream.
virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const;
/// Reads the delta-encoded non-property contents of this object from the specified stream.
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference);
/// \param reread if true, reread the contents from the stream but don't reapply them
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread = false);
/// Writes the subdivision of the contents of this object (preceeded by a
/// reference to the object itself) to the specified stream if necessary.

View file

@ -681,41 +681,17 @@ void HeightfieldHeightEditor::clear() {
}
static QByteArray encodeHeightfieldColor(int offsetX, int offsetY, int width, int height, const QByteArray& contents) {
QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE + contents.size(), 0);
QByteArray inflated(HEIGHTFIELD_DATA_HEADER_SIZE, 0);
qint32* header = (qint32*)inflated.data();
*header++ = offsetX;
*header++ = offsetY;
*header++ = width;
*header++ = height;
if (!contents.isEmpty()) {
// encode with Paeth filter (see http://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering)
const uchar* src = (const uchar*)contents.constData();
uchar* dest = (uchar*)inflated.data() + HEIGHTFIELD_DATA_HEADER_SIZE;
*dest++ = *src++;
*dest++ = *src++;
*dest++ = *src++;
int stride = width * DataBlock::COLOR_BYTES;
for (uchar* end = dest + stride - DataBlock::COLOR_BYTES; dest != end; dest++, src++) {
*dest = *src - src[-DataBlock::COLOR_BYTES];
}
for (int y = 1; y < height; y++) {
*dest++ = *src - src[-stride];
src++;
*dest++ = *src - src[-stride];
src++;
*dest++ = *src - src[-stride];
src++;
for (uchar* end = dest + stride - DataBlock::COLOR_BYTES; dest != end; dest++, src++) {
int a = src[-DataBlock::COLOR_BYTES];
int b = src[-stride];
int c = src[-stride - DataBlock::COLOR_BYTES];
int p = a + b - c;
int ad = abs(a - p);
int bd = abs(b - p);
int cd = abs(c - p);
*dest = *src - (ad < bd ? (ad < cd ? a : c) : (bd < cd ? b : c));
}
}
QBuffer buffer(&inflated);
buffer.open(QIODevice::WriteOnly | QIODevice::Append);
QImage((const uchar*)contents.constData(), width, height, width * DataBlock::COLOR_BYTES,
QImage::Format_RGB888).save(&buffer, "JPG");
}
return qCompress(inflated);
}
@ -727,35 +703,19 @@ static QByteArray decodeHeightfieldColor(const QByteArray& encoded, int& offsetX
offsetY = *header++;
width = *header++;
height = *header++;
QByteArray contents(inflated.size() - HEIGHTFIELD_DATA_HEADER_SIZE, 0);
if (!contents.isEmpty()) {
const uchar* src = (const uchar*)inflated.constData() + HEIGHTFIELD_DATA_HEADER_SIZE;
uchar* dest = (uchar*)contents.data();
*dest++ = *src++;
*dest++ = *src++;
*dest++ = *src++;
int stride = width * DataBlock::COLOR_BYTES;
for (uchar* end = dest + stride - DataBlock::COLOR_BYTES; dest != end; dest++, src++) {
*dest = *src + dest[-DataBlock::COLOR_BYTES];
}
for (int y = 1; y < height; y++) {
*dest = (*src++) + dest[-stride];
dest++;
*dest = (*src++) + dest[-stride];
dest++;
*dest = (*src++) + dest[-stride];
dest++;
for (uchar* end = dest + stride - DataBlock::COLOR_BYTES; dest != end; dest++, src++) {
int a = dest[-DataBlock::COLOR_BYTES];
int b = dest[-stride];
int c = dest[-stride - DataBlock::COLOR_BYTES];
int p = a + b - c;
int ad = abs(a - p);
int bd = abs(b - p);
int cd = abs(c - p);
*dest = *src + (ad < bd ? (ad < cd ? a : c) : (bd < cd ? b : c));
}
}
int payloadSize = inflated.size() - HEIGHTFIELD_DATA_HEADER_SIZE;
if (payloadSize == 0) {
return QByteArray();
}
QImage image = QImage::fromData((const uchar*)inflated.constData() + HEIGHTFIELD_DATA_HEADER_SIZE, payloadSize, "JPG");
if (image.format() != QImage::Format_RGB888) {
image = image.convertToFormat(QImage::Format_RGB888);
}
QByteArray contents(width * height * DataBlock::COLOR_BYTES, 0);
char* dest = contents.data();
int stride = width * DataBlock::COLOR_BYTES;
for (int y = 0; y < height; y++, dest += stride) {
memcpy(dest, image.constScanLine(y), stride);
}
return contents;
}
@ -3718,9 +3678,18 @@ void Heightfield::writeExtra(Bitstream& out) const {
_root->write(state);
}
void Heightfield::readExtra(Bitstream& in) {
void Heightfield::readExtra(Bitstream& in, bool reread) {
if (getWillBeVoxelized()) {
in >> _height >> _color >> _material >> _stack;
if (reread) {
HeightfieldHeightPointer height;
HeightfieldColorPointer color;
HeightfieldMaterialPointer material;
HeightfieldStackPointer stack;
in >> height >> color >> material >> stack;
} else {
in >> _height >> _color >> _material >> _stack;
}
return;
}
MetavoxelLOD lod;
@ -3732,7 +3701,9 @@ void Heightfield::readExtra(Bitstream& in) {
HeightfieldNodePointer root(new HeightfieldNode());
root->read(state);
setRoot(root);
if (!reread) {
setRoot(root);
}
}
void Heightfield::writeExtraDelta(Bitstream& out, const SharedObject* reference) const {
@ -3756,7 +3727,7 @@ void Heightfield::writeExtraDelta(Bitstream& out, const SharedObject* reference)
}
}
void Heightfield::readExtraDelta(Bitstream& in, const SharedObject* reference) {
void Heightfield::readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread) {
MetavoxelLOD lod, referenceLOD;
if (in.getContext()) {
MetavoxelStreamBase* base = static_cast<MetavoxelStreamBase*>(in.getContext());
@ -3766,16 +3737,21 @@ void Heightfield::readExtraDelta(Bitstream& in, const SharedObject* reference) {
HeightfieldStreamBase base = { in, lod, referenceLOD };
HeightfieldStreamState state = { base, glm::vec2(), 1.0f };
setRoot(static_cast<const Heightfield*>(reference)->getRoot());
bool changed;
in >> changed;
if (changed) {
HeightfieldNodePointer root(new HeightfieldNode());
root->readDelta(static_cast<const Heightfield*>(reference)->getRoot(), state);
setRoot(root);
if (!reread) {
setRoot(root);
}
} else if (state.becameSubdividedOrCollapsed()) {
setRoot(HeightfieldNodePointer(_root->readSubdivision(state)));
HeightfieldNodePointer root(_root->readSubdivision(state));
if (!reread) {
setRoot(root);
}
} else if (!reread) {
setRoot(static_cast<const Heightfield*>(reference)->getRoot());
}
}

View file

@ -820,9 +820,9 @@ public:
virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal);
virtual void writeExtra(Bitstream& out) const;
virtual void readExtra(Bitstream& in);
virtual void readExtra(Bitstream& in, bool reread);
virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const;
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference);
virtual void readExtraDelta(Bitstream& in, const SharedObject* reference, bool reread);
virtual void maybeWriteSubdivision(Bitstream& out);
virtual SharedObject* readSubdivision(Bitstream& in);

View file

@ -158,7 +158,8 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart) {
QHttpMultiPart* dataMultiPart,
const QVariantMap& propertyMap) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
@ -166,13 +167,15 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart));
Q_ARG(QHttpMultiPart*, dataMultiPart),
Q_ARG(QVariantMap, propertyMap));
}
void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart) {
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart,
const QVariantMap& propertyMap) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
@ -180,14 +183,16 @@ void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessM
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart));
Q_ARG(QHttpMultiPart*, dataMultiPart),
Q_ARG(QVariantMap, propertyMap));
}
void AccountManager::invokedRequest(const QString& path,
bool requiresAuthentication,
QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart,
const QVariantMap& propertyMap) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
@ -235,7 +240,9 @@ void AccountManager::invokedRequest(const QString& path,
} else {
networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
}
dataMultiPart->setParent(networkReply);
// make sure dataMultiPart is destroyed when the reply is
connect(networkReply, &QNetworkReply::destroyed, dataMultiPart, &QHttpMultiPart::deleteLater);
} else {
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (operation == QNetworkAccessManager::PostOperation) {
@ -255,6 +262,14 @@ void AccountManager::invokedRequest(const QString& path,
}
if (networkReply) {
if (!propertyMap.isEmpty()) {
// we have properties to set on the reply so the user can check them after
foreach(const QString& propertyKey, propertyMap.keys()) {
networkReply->setProperty(qPrintable(propertyKey), propertyMap.value(propertyKey));
}
}
if (!callbackParams.isEmpty()) {
// if we have information for a callback, insert the callbackParams into our local map
_pendingCallbackMap.insert(networkReply, callbackParams);

View file

@ -48,13 +48,15 @@ public:
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray(),
QHttpMultiPart* dataMultiPart = NULL);
QHttpMultiPart* dataMultiPart = NULL,
const QVariantMap& propertyMap = QVariantMap());
void unauthenticatedRequest(const QString& path,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray(),
QHttpMultiPart* dataMultiPart = NULL);
QHttpMultiPart* dataMultiPart = NULL,
const QVariantMap& propertyMap = QVariantMap()) ;
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
@ -109,7 +111,8 @@ private:
QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart);
QHttpMultiPart* dataMultiPart,
const QVariantMap& propertyMap);
QUrl _authURL;
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;

View file

@ -119,14 +119,16 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
if (!handleUsername(lookupUrl.authority())) {
// we're assuming this is either a network address or global place name
// check if it is a network address first
if (!handleNetworkAddress(lookupUrl.host()
if (handleNetworkAddress(lookupUrl.host()
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())))) {
// we may have a path that defines a relative viewpoint - if so we should jump to that now
handleRelativeViewpoint(lookupUrl.path());
} else {
// wasn't an address - lookup the place name
attemptPlaceNameLookup(lookupUrl.host());
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path());
}
// we may have a path that defines a relative viewpoint - if so we should jump to that now
handleRelativeViewpoint(lookupUrl.path());
}
return true;
@ -164,12 +166,14 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) {
QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object();
QJsonObject dataObject = responseObject["data"].toObject();
goToAddressFromObject(dataObject.toVariantMap());
goToAddressFromObject(dataObject.toVariantMap(), requestReply);
emit lookupResultsFinished();
}
void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) {
const char OVERRIDE_PATH_KEY[] = "override_path";
void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) {
const QString DATA_OBJECT_PLACE_KEY = "place";
const QString DATA_OBJECT_USER_LOCATION_KEY = "location";
@ -203,6 +207,8 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) {
if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) {
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
qDebug() << "Possible domain change required to connect to" << domainHostname
<< "on" << DEFAULT_DOMAIN_SERVER_PORT;
emit possibleDomainChangeRequired(domainHostname, DEFAULT_DOMAIN_SERVER_PORT);
} else {
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
@ -211,6 +217,9 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) {
QString domainIDString = domainObject[DOMAIN_ID_KEY].toString();
QUuid domainID(domainIDString);
qDebug() << "Possible domain change required to connect to domain with ID" << domainID
<< "via ice-server at" << iceServerAddress;
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);
}
@ -223,18 +232,29 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject) {
QString newRootPlaceName = rootMap[PLACE_NAME_KEY].toString();
setRootPlaceName(newRootPlaceName);
// take the path that came back
const QString PLACE_PATH_KEY = "path";
QString returnedPath = locationMap[PLACE_PATH_KEY].toString();
// check if we had a path to override the path returned
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY);
if (!returnedPath.isEmpty()) {
// try to parse this returned path as a viewpoint, that's the only thing it could be for now
if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) {
qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath;
if (!overridePath.isEmpty()) {
if (!handleRelativeViewpoint(overridePath)){
qDebug() << "User entered path could not be handled as a viewpoint - " << overridePath;
}
} else {
// take the path that came back
const QString PLACE_PATH_KEY = "path";
QString returnedPath = locationMap[PLACE_PATH_KEY].toString();
bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY);
if (!returnedPath.isEmpty()) {
// try to parse this returned path as a viewpoint, that's the only thing it could be for now
if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) {
qDebug() << "Received a location path that was could not be handled as a viewpoint -" << returnedPath;
}
}
}
} else {
qDebug() << "Received an address manager API response with no domain key. Cannot parse.";
qDebug() << locationMap;
@ -260,12 +280,21 @@ void AddressManager::handleAPIError(QNetworkReply& errorReply) {
const QString GET_PLACE = "/api/v1/places/%1";
void AddressManager::attemptPlaceNameLookup(const QString& lookupString) {
void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath) {
// assume this is a place name and see if we can get any info on it
QString placeName = QUrl::toPercentEncoding(lookupString);
QVariantMap requestParams;
if (!overridePath.isEmpty()) {
requestParams.insert(OVERRIDE_PATH_KEY, overridePath);
}
AccountManager::getInstance().unauthenticatedRequest(GET_PLACE.arg(placeName),
QNetworkAccessManager::GetOperation,
apiCallbackParameters());
apiCallbackParameters(),
QByteArray(),
NULL,
requestParams);
}
bool AddressManager::handleNetworkAddress(const QString& lookupString) {

View file

@ -47,7 +47,7 @@ public:
const QString& getRootPlaceName() const { return _rootPlaceName; }
void setRootPlaceName(const QString& rootPlaceName);
void attemptPlaceNameLookup(const QString& lookupString);
void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath = QString());
void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; }
void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
@ -57,7 +57,7 @@ public:
public slots:
void handleLookupString(const QString& lookupString);
void goToUser(const QString& username);
void goToAddressFromObject(const QVariantMap& addressMap);
void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply);
void storeCurrentAddress();

View file

@ -78,7 +78,7 @@ PacketVersion versionForPacketType(PacketType type) {
case PacketTypeAudioStreamStats:
return 1;
case PacketTypeMetavoxelData:
return 11;
return 12;
default:
return 0;
}

View file

@ -60,14 +60,17 @@ void PhysicsEngine::addEntityInternal(EntityItem* entity) {
assert(entity);
void* physicsInfo = entity->getPhysicsInfo();
if (!physicsInfo) {
EntityMotionState* motionState = new EntityMotionState(entity);
if (addObject(motionState)) {
ShapeInfo shapeInfo;
entity->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
EntityMotionState* motionState = new EntityMotionState(entity);
entity->setPhysicsInfo(static_cast<void*>(motionState));
_entityMotionStates.insert(motionState);
addObject(shapeInfo, shape, motionState);
} else {
// We failed to add the entity to the simulation. Probably because we couldn't create a shape for it.
//qDebug() << "failed to add entity " << entity->getEntityItemID() << " to physics engine";
delete motionState;
}
}
}
@ -244,59 +247,53 @@ void PhysicsEngine::stepSimulation() {
// CF_DISABLE_VISUALIZE_OBJECT = 32, //disable debug drawing
// CF_DISABLE_SPU_COLLISION_PROCESSING = 64//disable parallel/SPU processing
bool PhysicsEngine::addObject(ObjectMotionState* motionState) {
void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState) {
assert(shape);
assert(motionState);
ShapeInfo shapeInfo;
motionState->computeShapeInfo(shapeInfo);
btCollisionShape* shape = _shapeManager.getShape(shapeInfo);
if (shape) {
btVector3 inertia(0.0f, 0.0f, 0.0f);
float mass = 0.0f;
btRigidBody* body = NULL;
switch(motionState->computeMotionType()) {
case MOTION_TYPE_KINEMATIC: {
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
body->updateInertiaTensor();
motionState->_body = body;
motionState->addKinematicController();
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
break;
}
case MOTION_TYPE_DYNAMIC: {
mass = motionState->computeMass(shapeInfo);
shape->calculateLocalInertia(mass, inertia);
body = new btRigidBody(mass, motionState, shape, inertia);
body->updateInertiaTensor();
motionState->_body = body;
motionState->updateObjectVelocities();
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
break;
}
case MOTION_TYPE_STATIC:
default: {
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor();
motionState->_body = body;
break;
}
btVector3 inertia(0.0f, 0.0f, 0.0f);
float mass = 0.0f;
btRigidBody* body = NULL;
switch(motionState->computeMotionType()) {
case MOTION_TYPE_KINEMATIC: {
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
body->updateInertiaTensor();
motionState->_body = body;
motionState->addKinematicController();
const float KINEMATIC_LINEAR_VELOCITY_THRESHOLD = 0.01f; // 1 cm/sec
const float KINEMATIC_ANGULAR_VELOCITY_THRESHOLD = 0.01f; // ~1 deg/sec
body->setSleepingThresholds(KINEMATIC_LINEAR_VELOCITY_THRESHOLD, KINEMATIC_ANGULAR_VELOCITY_THRESHOLD);
break;
}
case MOTION_TYPE_DYNAMIC: {
mass = motionState->computeMass(shapeInfo);
shape->calculateLocalInertia(mass, inertia);
body = new btRigidBody(mass, motionState, shape, inertia);
body->updateInertiaTensor();
motionState->_body = body;
motionState->updateObjectVelocities();
// NOTE: Bullet will deactivate any object whose velocity is below these thresholds for longer than 2 seconds.
// (the 2 seconds is determined by: static btRigidBody::gDeactivationTime
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
body->setSleepingThresholds(DYNAMIC_LINEAR_VELOCITY_THRESHOLD, DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
break;
}
case MOTION_TYPE_STATIC:
default: {
body = new btRigidBody(mass, motionState, shape, inertia);
body->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
body->updateInertiaTensor();
motionState->_body = body;
break;
}
// wtf?
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
body->setRestitution(motionState->_restitution);
body->setFriction(motionState->_friction);
body->setDamping(motionState->_linearDamping, motionState->_angularDamping);
_dynamicsWorld->addRigidBody(body);
return true;
}
return false;
body->setFlags(BT_DISABLE_WORLD_GRAVITY);
body->setRestitution(motionState->_restitution);
body->setFriction(motionState->_friction);
body->setDamping(motionState->_linearDamping, motionState->_angularDamping);
_dynamicsWorld->addRigidBody(body);
}
bool PhysicsEngine::removeObject(ObjectMotionState* motionState) {

View file

@ -59,7 +59,7 @@ public:
/// \param motionState pointer to Object's MotionState
/// \return true if Object added
bool addObject(ObjectMotionState* motionState);
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
/// \param motionState pointer to Object's MotionState
/// \return true if Object removed