diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 56b710c5dd..f57e79e974 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -305,7 +305,7 @@ function controller(hand, triggerAction) { this.activateEntity(this.grabbedEntity); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, "position"); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]); var handRotation = this.getHandRotation(); var handPosition = this.getHandPosition(); diff --git a/examples/cubePerfTest.js b/examples/cubePerfTest.js index 699472edd9..f2f4d48b22 100644 --- a/examples/cubePerfTest.js +++ b/examples/cubePerfTest.js @@ -36,7 +36,7 @@ for (var x = 0; x < SIDE_SIZE; x++) { var position = Vec3.sum(MyAvatar.position, { x: x * 0.2, y: y * 0.2, z: z * 0.2}); var radius = Math.random() * 0.1; boxes.push(Entities.addEntity({ - type: cube ? "Box" : "Box", + type: cube ? "Box" : "Sphere", name: "PerfTest", position: position, dimensions: { x: radius, y: radius, z: radius }, @@ -52,7 +52,7 @@ for (var x = 0; x < SIDE_SIZE; x++) { function scriptEnding() { for (var i = 0; i < boxes.length; i++) { - //Entities.deleteEntity(boxes[i]); + Entities.deleteEntity(boxes[i]); } } Script.scriptEnding.connect(scriptEnding); diff --git a/examples/example/entities/platform.js b/examples/example/entities/platform.js new file mode 100644 index 0000000000..1748198cce --- /dev/null +++ b/examples/example/entities/platform.js @@ -0,0 +1,1221 @@ +// +// platform.js +// +// Created by Seiji Emery on 8/19/15 +// Copyright 2015 High Fidelity, Inc. +// +// Entity stress test / procedural demo. +// Spawns a platform under your avatar made up of randomly sized and colored boxes or spheres. The platform follows your avatar +// around, and comes with a UI to update the platform's properties (radius, entity density, color distribution, etc) in real time. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +// UI and debug console implemented using uiwidgets / 2d overlays +Script.include("../../libraries/uiwidgets.js"); +if (typeof(UI) === 'undefined') { // backup link in case the user downloaded this somewhere + print("Missing library script -- loading from public.highfidelity.io"); + Script.include('http://public.highfidelity.io/scripts/libraries/uiwidgets.js'); + if (typeof(UI) === 'undefined') { + print("Cannot load UIWidgets library -- check your internet connection", COLORS.RED); + throw new Error("Could not load uiwidgets.js"); + } +} + +// Platform script +(function () { +var SCRIPT_NAME = "platform.js"; +var USE_DEBUG_LOG = true; // Turns on the 2dOverlay-based debug log. If false, just redirects to print. +var NUM_DEBUG_LOG_LINES = 10; +var LOG_ENTITY_CREATION_MESSAGES = false; // detailed debugging (init) +var LOG_UPDATE_STATUS_MESSAGES = false; // detailed debugging (startup) + +var MAX_UPDATE_INTERVAL = 0.2; // restrict to 5 updates / sec +var AVATAR_HEIGHT_OFFSET = 1.5; // offset to make the platform spawn under your feet. Might need to be adjusted for unusually proportioned avatars. + +var USE_ENTITY_TIMEOUTS = true; +var ENTITY_TIMEOUT_DURATION = 30.0; // kill entities in 30 secs if they don't get any updates +var ENTITY_REFRESH_INTERVAL = 10.0; // poke the entities every 10s so they don't die until we're done with them + +// Initial state +var NUM_PLATFORM_ENTITIES = 400; +var RADIUS = 5.0; + +// Defines min/max for onscreen platform radius, density, and entity width/height/depth sliders. +// Color limits are hardcoded at [0, 255]. +var PLATFORM_RADIUS_RANGE = [ 1.0, 15.0 ]; +var PLATFORM_DENSITY_RANGE = [ 0.0, 35.0 ]; // do NOT increase this above 40! (~20k limit). Entity count = Math.PI * radius * radius * density. +var PLATFORM_SHAPE_DIMENSIONS_RANGE = [ 0.001, 2.0 ]; // axis-aligned entity dimension limits + +// Utils +(function () { + if (typeof(Math.randRange) === 'undefined') { + Math.randRange = function (min, max) { + return Math.random() * (max - min) + min; + } + } + if (typeof(Math.randInt) === 'undefined') { + Math.randInt = function (n) { + return Math.floor(Math.random() * n) | 0; + } + } + function fromComponents (r, g, b, a) { + this.red = r; + this.green = g; + this.blue = b; + this.alpha = a || 1.0; + } + function fromHex (c) { + this.red = parseInt(c[1] + c[2], 16); + this.green = parseInt(c[3] + c[4], 16); + this.blue = parseInt(c[5] + c[6], 16); + } + var Color = this.Color = function () { + if (arguments.length >= 3) { + fromComponents.apply(this, arguments); + } else if (arguments.length == 1 && arguments[0].length == 7 && arguments[0][0] == '#') { + fromHex.apply(this, arguments); + } else { + throw new Error("Invalid arguments to new Color(): " + JSON.stringify(arguments)); + } + } + Color.prototype.toString = function () { + return "[Color: " + JSON.stringify(this) + "]"; + } +})(); + +// RNG models +(function () { + /// Encapsulates a simple color model that generates colors using a linear, pseudo-random color distribution. + var RandomColorModel = this.RandomColorModel = function () { + this.shadeRange = 0; // = 200; + this.minColor = 55; // = 100; + this.redRange = 255; // = 200; + this.greenRange = 0; // = 10; + this.blueRange = 0; // = 0; + }; + /// Generates 4 numbers in [0, 1] corresponding to each color attribute (uniform shade and additive red, green, blue). + /// This is done in a separate step from actually generating the colors, since it allows us to either A) completely + /// rebuild / re-randomize the color values, or B) reuse the RNG values but with different color parameters, which + /// enables us to do realtime color editing on the same visuals (awesome!). + RandomColorModel.prototype.generateSeed = function () { + return [ Math.random(), Math.random(), Math.random(), Math.random() ]; + }; + /// Takes a random 'seed' (4 floats from this.generateSeed()) and calculates a pseudo-random + /// color by combining that with the color model's current parameters. + RandomColorModel.prototype.getRandom = function (r) { + // logMessage("color seed values " + JSON.stringify(r)); + var shade = Math.min(255, this.minColor + r[0] * this.shadeRange); + + // No clamping on the color components, so they may overflow. + // However, this creates some pretty interesting visuals, so we're not "fixing" this. + var color = { + red: shade + r[1] * this.redRange, + green: shade + r[2] * this.greenRange, + blue: shade + r[3] * this.blueRange + }; + // logMessage("this: " + JSON.stringify(this)); + // logMessage("color: " + JSON.stringify(color), COLORS.RED); + return color; + }; + /// Custom property iterator used to setup UI (sliders, etc) + RandomColorModel.prototype.setupUI = function (callback) { + var _this = this; + [ + ['shadeRange', 'shade range'], + ['minColor', 'shade min'], + ['redRange', 'red (additive)'], + ['greenRange', 'green (additive)'], + ['blueRange', 'blue (additive)'] + ].forEach(function (v) { + // name, value, min, max, onValueChanged + callback(v[1], _this[v[0]], 0, 255, function (value) { _this[v[0]] = value }); + }); + } + + /// Generates pseudo-random dimensions for our cubes / shapes. + var RandomShapeModel = this.RandomShapeModel = function () { + this.widthRange = [ 0.3, 0.7 ]; + this.depthRange = [ 0.5, 0.8 ]; + this.heightRange = [ 0.01, 0.08 ]; + }; + /// Generates 3 seed numbers in [0, 1] + RandomShapeModel.prototype.generateSeed = function () { + return [ Math.random(), Math.random(), Math.random() ]; + } + /// Combines seed values with width/height/depth ranges to produce vec3 dimensions for a cube / sphere. + RandomShapeModel.prototype.getRandom = function (r) { + return { + x: r[0] * (this.widthRange[1] - this.widthRange[0]) + this.widthRange[0], + y: r[1] * (this.heightRange[1] - this.heightRange[0]) + this.heightRange[0], + z: r[2] * (this.depthRange[1] - this.depthRange[0]) + this.depthRange[0] + }; + } + /// Custom property iterator used to setup UI (sliders, etc) + RandomShapeModel.prototype.setupUI = function (callback) { + var _this = this; + var dimensionsMin = PLATFORM_SHAPE_DIMENSIONS_RANGE[0]; + var dimensionsMax = PLATFORM_SHAPE_DIMENSIONS_RANGE[1]; + [ + ['widthRange', 'width'], + ['depthRange', 'depth'], + ['heightRange', 'height'] + ].forEach(function (v) { + // name, value, min, max, onValueChanged + callback(v[1], _this[v[0]], dimensionsMin, dimensionsMax, function (value) { _this[v[0]] = value }); + }); + } + + /// Combines color + shape PRNG models and hides their implementation details. + var RandomAttribModel = this.RandomAttribModel = function () { + this.colorModel = new RandomColorModel(); + this.shapeModel = new RandomShapeModel(); + } + /// Completely re-randomizes obj's `color` and `dimensions` parameters based on the current model params. + RandomAttribModel.prototype.randomizeShapeAndColor = function (obj) { + // logMessage("randomizing " + JSON.stringify(obj)); + obj._colorSeed = this.colorModel.generateSeed(); + obj._shapeSeed = this.shapeModel.generateSeed(); + this.updateShapeAndColor(obj); + // logMessage("color seed: " + JSON.stringify(obj._colorSeed), COLORS.RED); + // logMessage("randomized color: " + JSON.stringify(obj.color), COLORS.RED); + // logMessage("randomized: " + JSON.stringify(obj)); + return obj; + } + /// Updates obj's `color` and `dimensions` params to use the current model params. + /// Reuses hidden seed attribs; _must_ have called randomizeShapeAndColor(obj) at some point before + /// calling this. + RandomAttribModel.prototype.updateShapeAndColor = function (obj) { + try { + // logMessage("update shape and color: " + this.colorModel); + obj.color = this.colorModel.getRandom(obj._colorSeed); + obj.dimensions = this.shapeModel.getRandom(obj._shapeSeed); + } catch (e) { + logMessage("update shape / color failed", COLORS.RED); + logMessage('' + e, COLORS.RED); + logMessage("obj._colorSeed = " + JSON.stringify(obj._colorSeed)); + logMessage("obj._shapeSeed = " + JSON.stringify(obj._shapeSeed)); + // logMessage("obj = " + JSON.stringify(obj)); + throw e; + } + return obj; + } +})(); + +// Status / logging UI (ignore this) +(function () { + var COLORS = this.COLORS = { + 'GREEN': new Color("#2D870C"), + 'RED': new Color("#AF1E07"), + 'LIGHT_GRAY': new Color("#CCCCCC"), + 'DARK_GRAY': new Color("#4E4E4E") + }; + function buildDebugLog () { + var LINE_WIDTH = 400; + var LINE_HEIGHT = 20; + + var lines = []; + var lineIndex = 0; + for (var i = 0; i < NUM_DEBUG_LOG_LINES; ++i) { + lines.push(new UI.Label({ + text: " ", visible: false, + width: LINE_WIDTH, height: LINE_HEIGHT, + })); + } + var title = new UI.Label({ + text: SCRIPT_NAME, visible: true, + width: LINE_WIDTH, height: LINE_HEIGHT, + }); + + var overlay = new UI.Box({ + visible: true, + width: LINE_WIDTH, height: 0, + backgroundColor: COLORS.DARK_GRAY, + backgroundAlpha: 0.3 + }); + overlay.setPosition(280, 10); + relayoutFrom(0); + UI.updateLayout(); + + function relayoutFrom (n) { + var layoutPos = { + x: overlay.position.x, + y: overlay.position.y + }; + + title.setPosition(layoutPos.x, layoutPos.y); + layoutPos.y += LINE_HEIGHT; + + // for (var i = n; i >= 0; --i) { + for (var i = n + 1; i < lines.length; ++i) { + if (lines[i].visible) { + lines[i].setPosition(layoutPos.x, layoutPos.y); + layoutPos.y += LINE_HEIGHT; + } + } + // for (var i = lines.length - 1; i > n; --i) { + for (var i = 0; i <= n; ++i) { + if (lines[i].visible) { + lines[i].setPosition(layoutPos.x, layoutPos.y); + layoutPos.y += LINE_HEIGHT; + } + } + overlay.height = (layoutPos.y - overlay.position.y + 10); + overlay.getOverlay().update({ + height: overlay.height + }); + } + this.logMessage = function (text, color, alpha) { + lines[lineIndex].setVisible(true); + relayoutFrom(lineIndex); + + lines[lineIndex].getOverlay().update({ + text: text, + visible: true, + color: color || COLORS.LIGHT_GRAY, + alpha: alpha !== undefined ? alpha : 1.0, + x: lines[lineIndex].position.x, + y: lines[lineIndex].position.y + }); + lineIndex = (lineIndex + 1) % lines.length; + UI.updateLayout(); + } + } + if (USE_DEBUG_LOG) { + buildDebugLog(); + } else { + this.logMessage = function (msg) { + print(SCRIPT_NAME + ": " + msg); + } + } +})(); + +// Utils (ignore) +(function () { + // Utility function + var withDefaults = this.withDefaults = function (properties, defaults) { + // logMessage("withDefaults: " + JSON.stringify(properties) + JSON.stringify(defaults)); + properties = properties || {}; + if (defaults) { + for (var k in defaults) { + properties[k] = defaults[k]; + } + } + return properties; + } + var withReadonlyProp = this.withReadonlyProp = function (propname, value, obj) { + Object.defineProperty(obj, propname, { + value: value, + writable: false + }); + return obj; + } + + // Math utils + if (typeof(Math.randRange) === 'undefined') { + Math.randRange = function (min, max) { + return Math.random() * (max - min) + min; + } + } + if (typeof(Math.randInt) === 'undefined') { + Math.randInt = function (n) { + return Math.floor(Math.random() * n) | 0; + } + } + + /// Random distrib: Get a random point within a circle on the xz plane with radius r, center p. + this.randomCirclePoint = function (r, pos) { + var a = Math.random(), b = Math.random(); + if (b < a) { + var tmp = b; + b = a; + a = tmp; + } + var point = { + x: pos.x + b * r * Math.cos(2 * Math.PI * a / b), + y: pos.y, + z: pos.z + b * r * Math.sin(2 * Math.PI * a / b) + }; + if (LOG_ENTITY_CREATION_MESSAGES) { + // logMessage("input params: " + JSON.stringify({ radius: r, position: pos }), COLORS.GREEN); + // logMessage("a = " + a + ", b = " + b); + logMessage("generated point: " + JSON.stringify(point), COLORS.RED); + } + return point; + } + + // Entity utils. NOT using overlayManager for... reasons >.> + var makeEntity = this.makeEntity = function (properties) { + if (LOG_ENTITY_CREATION_MESSAGES) { + logMessage("Creating entity: " + JSON.stringify(properties)); + } + var entity = Entities.addEntity(properties); + return withReadonlyProp("type", properties.type, { + update: function (properties) { + Entities.editEntity(entity, properties); + }, + destroy: function () { + Entities.deleteEntity(entity) + }, + getId: function () { + return entity; + } + }); + } + // this.makeLight = function (properties) { + // return makeEntity(withDefaults(properties, { + // type: "Light", + // isSpotlight: false, + // diffuseColor: { red: 255, green: 100, blue: 100 }, + // ambientColor: { red: 200, green: 80, blue: 80 } + // })); + // } + this.makeBox = function (properties) { + // logMessage("Creating box: " + JSON.stringify(properties)); + return makeEntity(withDefaults(properties, { + type: "Box" + })); + } +})(); + +// Platform +(function () { + /// Encapsulates a platform 'piece'. Owns an entity (`box`), and handles destruction and some other state. + var PlatformComponent = this.PlatformComponent = function (properties) { + // logMessage("Platform component initialized with " + Object.keys(properties), COLORS.GREEN); + this.position = properties.position || null; + this.color = properties.color || null; + this.dimensions = properties.dimensions || null; + this.entityType = properties.type || "Box"; + + // logMessage("Spawning with type: '" + this.entityType + "' (properties.type = '" + properties.type + "')", COLORS.GREEN); + + if (properties._colorSeed) + this._colorSeed = properties._colorSeed; + if (properties._shapeSeed) + this._shapeSeed = properties._shapeSeed; + + // logMessage("dimensions: " + JSON.stringify(this.dimensions)); + // logMessage("color: " + JSON.stringify(this.color)); + + this.cachedEntity = null; + this.activeEntity = this.spawnEntity(this.entityType); + }; + PlatformComponent.prototype.spawnEntity = function (type) { + return makeEntity({ + type: type, + position: this.position, + dimensions: this.dimensions, + color: this.color, + lifetime: USE_ENTITY_TIMEOUTS ? ENTITY_TIMEOUT_DURATION : -1.0, + alpha: 0.5 + }); + } + if (USE_ENTITY_TIMEOUTS) { + PlatformComponent.prototype.pokeEntity = function () { + // Kinda inefficient, but there's no way to get around this :/ + var age = Entities.getEntityProperties(this.activeEntity.getId()).age; + this.activeEntity.update({ lifetime: ENTITY_TIMEOUT_DURATION + age }); + } + } else { + PlatformComponent.prototype.pokeEntity = function () {} + } + /// Updates platform to be at position p, and calls .update() with the current + /// position, color, and dimensions parameters. + PlatformComponent.prototype.update = function (position) { + if (position) + this.position = position; + // logMessage("updating with " + JSON.stringify(this)); + this.activeEntity.update(this); + } + function swap (a, b) { + var tmp = a; + a = b; + b = tmp; + } + PlatformComponent.prototype.swapEntityType = function (newType) { + if (this.entityType !== newType) { + this.entityType = newType; + // logMessage("Destroying active entity and rebuilding it (newtype = '" + newType + "')"); + if (this.activeEntity) { + this.activeEntity.destroy(); + } + this.activeEntity = this.spawnEntity(newType); + // if (this.cachedEntity && this.cachedEntity.type == newType) { + // this.cachedEntity.update({ visible: true }); + // this.activeEntity.update({ visible: false }); + // swap(this.cachedEntity, this.activeEntity); + // this.update(this.position); + // } else { + // this.activeEntity.update({ visible: false }); + // this.cachedEntity = this.activeEntity; + // this.activeEntity = spawnEntity(newType); + // } + } + } + /// Swap state with another component + PlatformComponent.prototype.swap = function (other) { + swap(this.position, other.position); + swap(this.dimensions, other.dimensions); + swap(this.color, other.color); + swap(this.entityType, other.entityType); + swap(this.activeEntity, other.activeEntity); + swap(this._colorSeed, other._colorSeed); + swap(this._shapeSeed, other._shapeSeed); + } + PlatformComponent.prototype.destroy = function () { + if (this.activeEntity) { + this.activeEntity.destroy(); + this.activeEntity = null; + } + if (this.cachedEntity) { + this.cachedEntity.destroy(); + this.cachedEntity = null; + } + } + + // util + function inRange (p1, p2, radius) { + return Vec3.distance(p1, p2) < Math.abs(radius); + } + + /// Encapsulates a moving platform that follows the avatar around (mostly). + var DynamicPlatform = this.DynamicPlatform = function (n, position, radius) { + this.position = position; + this.radius = radius; + this.randomizer = new RandomAttribModel(); + this.boxType = "Box"; + this.boxTypes = [ "Box", "Sphere" ]; + + logMessage("Spawning " + n + " entities", COLORS.GREEN); + var boxes = this.boxes = []; + while (n > 0) { + boxes.push(this.spawnEntity()); + --n; + } + this.targetDensity = this.getEntityDensity(); + this.pendingUpdates = {}; + this.updateTimer = 0.0; + + this.platformHeight = position.y; + this.oldPos = { x: position.x, y: position.y, z: position.z }; + this.oldRadius = radius; + + // this.sendPokes(); + } + DynamicPlatform.prototype.toString = function () { + return "[DynamicPlatform (" + this.boxes.length + " entities)]"; + } + DynamicPlatform.prototype.spawnEntity = function () { + // logMessage("Called spawn entity. this.boxType = '" + this.boxType + "'") + var properties = { position: this.randomPoint(), type: this.boxType }; + this.randomizer.randomizeShapeAndColor(properties); + return new PlatformComponent(properties); + } + DynamicPlatform.prototype.updateEntityAttribs = function () { + var _this = this; + this.setPendingUpdate('updateEntityAttribs', function () { + // logMessage("updating model", COLORS.GREEN); + _this.boxes.forEach(function (box) { + this.randomizer.updateShapeAndColor(box); + box.update(); + }, _this); + }); + } + DynamicPlatform.prototype.toggleBoxType = function () { + var _this = this; + this.setPendingUpdate('toggleBoxType', function () { + // Swap / cycle through types: find index of current type and set next type to idx+1 + for (var idx = 0; idx < _this.boxTypes.length; ++idx) { + if (_this.boxTypes[idx] === _this.boxType) { + var nextIndex = (idx + 1) % _this.boxTypes.length; + logMessage("swapping box type from '" + _this.boxType + "' to '" + _this.boxTypes[nextIndex] + "'", COLORS.GREEN); + _this.boxType = _this.boxTypes[nextIndex]; + break; + } + } + _this.boxes.forEach(function (box) { + box.swapEntityType(_this.boxType); + }, _this); + }); + } + DynamicPlatform.prototype.getBoxType = function () { + return this.boxType; + } + + // if (USE_ENTITY_TIMEOUTS) { + // DynamicPlatform.prototype.sendPokes = function () { + // var _this = this; + // function poke () { + // logMessage("Poking entities so they don't die", COLORS.GREEN); + // _this.boxes.forEach(function (box) { + // box.pokeEntity(); + // }, _this); + + + // if (_this.pendingUpdates['keepalive']) { + // logMessage("previous timer: " + _this.pendingUpdates['keepalive'].timer + "; new timer: " + ENTITY_REFRESH_INTERVAL) + // } + // _this.pendingUpdates['keepalive'] = { + // callback: poke, + // timer: ENTITY_REFRESH_INTERVAL, + // skippedUpdates: 0 + // }; + // // _this.setPendingUpdate('keepalive', poke); + // // _this.pendingUpdates['keepalive'].timer = ENTITY_REFRESH_INTERVAL; + // } + // poke(); + // } + // } else { + // DynamicPlatform.prototype.sendPokes = function () {}; + // } + + /// Queue impl that uses the update loop to limit potentially expensive updates to only execute every x seconds (default: 200 ms). + /// This is to prevent UI code from running full entity updates every 10 ms (or whatever). + DynamicPlatform.prototype.setPendingUpdate = function (name, callback) { + if (!this.pendingUpdates[name]) { + // logMessage("Queued update for " + name, COLORS.GREEN); + this.pendingUpdates[name] = { + callback: callback, + timer: 0.0, + skippedUpdates: 0 + } + } else { + // logMessage("Deferred update for " + name, COLORS.GREEN); + this.pendingUpdates[name].callback = callback; + this.pendingUpdates[name].skippedUpdates++; + // logMessage("scheduling update for \"" + name + "\" to run in " + this.pendingUpdates[name].timer + " seconds"); + } + } + /// Runs all queued updates as soon as they can execute (each one has a cooldown timer). + DynamicPlatform.prototype.processPendingUpdates = function (dt) { + for (var k in this.pendingUpdates) { + if (this.pendingUpdates[k].timer >= 0.0) + this.pendingUpdates[k].timer -= dt; + + if (this.pendingUpdates[k].callback && this.pendingUpdates[k].timer < 0.0) { + // logMessage("Dispatching update for " + k); + try { + this.pendingUpdates[k].callback(); + } catch (e) { + logMessage("update for \"" + k + "\" failed: " + e, COLORS.RED); + } + this.pendingUpdates[k].timer = MAX_UPDATE_INTERVAL; + this.pendingUpdates[k].skippedUpdates = 0; + this.pendingUpdates[k].callback = null; + } else { + // logMessage("Deferred update for " + k + " for " + this.pendingUpdates[k].timer + " seconds"); + } + } + } + + /// Updates the platform based on the avatar's current position (spawning / despawning entities as needed), + /// and calls processPendingUpdates() once this is done. + /// Does NOT have any update interval limits (it just updates every time it gets run), but these are not full + /// updates (they're incremental), so the network will not get flooded so long as the avatar is moving at a + /// normal walking / flying speed. + DynamicPlatform.prototype.updatePosition = function (dt, position) { + // logMessage("updating " + this); + position.y = this.platformHeight; + this.position = position; + + var toUpdate = []; + this.boxes.forEach(function (box, i) { + // if (Math.abs(box.position.y - position.y) > HEIGHT_TOLERANCE || !inRange(box, position, radius)) { + if (!inRange(box.position, this.position, this.radius)) { + toUpdate.push(i); + } + }, this); + + var MAX_TRIES = toUpdate.length * 8; + var tries = MAX_TRIES; + var moved = 0; + var recalcs = 0; + toUpdate.forEach(function (index) { + if ((index % 2 == 0) || tries > 0) { + do { + var randomPoint = this.randomPoint(this.position, this.radius); + ++recalcs + } while (--tries > 0 && inRange(randomPoint, this.oldPos, this.oldRadiuss)); + + if (LOG_UPDATE_STATUS_MESSAGES && tries <= 0) { + logMessage("updatePlatform() gave up after " + MAX_TRIES + " iterations (" + moved + " / " + toUpdate.length + " successful updates)", COLORS.RED); + logMessage("old pos: " + JSON.stringify(this.oldPos) + ", old radius: " + this.oldRadius); + logMessage("new pos: " + JSON.stringify(this.position) + ", new radius: " + this.radius); + } + } else { + var randomPoint = this.randomPoint(position, this.radius); + } + + this.randomizer.randomizeShapeAndColor(this.boxes[index]); + this.boxes[index].update(randomPoint); + // this.boxes[index].setValues({ + // position: randomPoint, + // // dimensions: this.randomDimensions(), + // // color: this.randomColor() + // }); + ++moved; + }, this); + recalcs = recalcs - toUpdate.length; + + this.oldPos = position; + this.oldRadius = this.radius; + if (LOG_UPDATE_STATUS_MESSAGES && toUpdate.length > 0) { + logMessage("updated " + toUpdate.length + " entities w/ " + recalcs + " recalcs"); + } + } + + DynamicPlatform.prototype.update = function (dt, position) { + this.updatePosition(dt, position); + this.processPendingUpdates(dt); + this.sendPokes(dt); + } + + if (USE_ENTITY_TIMEOUTS) { + DynamicPlatform.prototype.sendPokes = function (dt) { + logMessage("starting keepalive", COLORS.GREEN); + // logMessage("dt = " + dt, COLORS.RED); + // var original = this.sendPokes; + var pokeTimer = 0.0; + this.sendPokes = function (dt) { + // logMessage("dt = " + dt); + if ((pokeTimer -= dt) < 0.0) { + // logMessage("Poking entities so they don't die", COLORS.GREEN); + this.boxes.forEach(function (box) { + box.pokeEntity(); + }, this); + pokeTimer = ENTITY_REFRESH_INTERVAL; + } else { + // logMessage("Poking entities in " + pokeTimer + " seconds"); + } + } + // logMessage("this.sendPokes === past this.sendPokes? " + (this.sendPokes === original), COLORS.GREEN); + this.sendPokes(dt); + } + } else { + DynamicPlatform.prototype.sendPokes = function () {}; + } + DynamicPlatform.prototype.getEntityCount = function () { + return this.boxes.length; + } + DynamicPlatform.prototype.getEntityCountWithRadius = function (radius) { + var est = Math.floor((radius * radius) / (this.radius * this.radius) * this.getEntityCount()); + var actual = Math.floor(Math.PI * radius * radius * this.getEntityDensity()); + + if (est != actual) { + logMessage("assert failed: getEntityCountWithRadius() -- est " + est + " != actual " + actual); + } + return est; + } + DynamicPlatform.prototype.getEntityCountWithDensity = function (density) { + return Math.floor(Math.PI * this.radius * this.radius * density); + } + + /// Sets the entity count to n. Don't call this directly -- use setRadius / density instead. + DynamicPlatform.prototype.setEntityCount = function (n) { + if (n > this.boxes.length) { + // logMessage("Setting entity count to " + n + " (adding " + (n - this.boxes.length) + " entities)", COLORS.GREEN); + + // Spawn new boxes + n = n - this.boxes.length; + for (; n > 0; --n) { + // var properties = { position: this.randomPoint() }; + // this.randomizer.randomizeShapeAndColor(properties); + // this.boxes.push(new PlatformComponent(properties)); + this.boxes.push(this.spawnEntity()); + } + } else if (n < this.boxes.length) { + // logMessage("Setting entity count to " + n + " (removing " + (this.boxes.length - n) + " entities)", COLORS.GREEN); + + // Destroy random boxes (technically, the most recent ones, but it should be sorta random) + n = this.boxes.length - n; + for (; n > 0; --n) { + this.boxes.pop().destroy(); + } + } + } + /// Calculate the entity density based on radial surface area. + DynamicPlatform.prototype.getEntityDensity = function () { + return (this.boxes.length * 1.0) / (Math.PI * this.radius * this.radius); + } + /// Queues a setDensity update. This is expensive, so we don't call it directly from UI. + DynamicPlatform.prototype.setDensityOnNextUpdate = function (density) { + var _this = this; + this.targetDensity = density; + this.setPendingUpdate('density', function () { + _this.updateEntityDensity(density); + }); + } + DynamicPlatform.prototype.updateEntityDensity = function (density) { + this.setEntityCount(Math.floor(density * Math.PI * this.radius * this.radius)); + } + DynamicPlatform.prototype.getRadius = function () { + return this.radius; + } + /// Queues a setRadius update. This is expensive, so we don't call it directly from UI. + DynamicPlatform.prototype.setRadiusOnNextUpdate = function (radius) { + var _this = this; + this.setPendingUpdate('radius', function () { + _this.setRadius(radius); + }); + } + var DEBUG_RADIUS_RECALC = false; + DynamicPlatform.prototype.setRadius = function (radius) { + if (radius < this.radius) { // Reduce case + // logMessage("Setting radius to " + radius + " (shrink by " + (this.radius - radius) + ")", COLORS.GREEN ); + this.radius = radius; + + // Remove all entities outside of current bounds. Requires swapping, since we want to maintain a contiguous array. + // Algorithm: two pointers at front and back. We traverse fwd and back, swapping elems so that all entities in bounds + // are at the front of the array, and all entities out of bounds are at the back. We then pop + destroy all entities + // at the back to reduce the entity count. + var count = this.boxes.length; + var toDelete = 0; + var swapList = []; + if (DEBUG_RADIUS_RECALC) { + logMessage("starting at i = 0, j = " + (count - 1)); + } + for (var i = 0, j = count - 1; i < j; ) { + // Find first elem outside of bounds that we can move to the back + while (inRange(this.boxes[i].position, this.position, this.radius) && i < j) { + ++i; + } + // Find first elem in bounds that we can move to the front + while (!inRange(this.boxes[j].position, this.position, this.radius) && i < j) { + --j; ++toDelete; + } + if (i < j) { + // swapList.push([i, j]); + if (DEBUG_RADIUS_RECALC) { + logMessage("swapping " + i + ", " + j); + } + this.boxes[i].swap(this.boxes[j]); + ++i, --j; ++toDelete; + } else { + if (DEBUG_RADIUS_RECALC) { + logMessage("terminated at i = " + i + ", j = " + j, COLORS.RED); + } + } + } + if (DEBUG_RADIUS_RECALC) { + logMessage("toDelete = " + toDelete, COLORS.RED); + } + // Sanity check + if (toDelete > this.boxes.length) { + logMessage("Error: toDelete " + toDelete + " > entity count " + this.boxes.length + " (setRadius algorithm)", COLORS.RED); + toDelete = this.boxes.length; + } + if (toDelete > 0) { + // logMessage("Deleting " + toDelete + " entities as part of radius resize", COLORS.GREEN); + } + // Delete cleared boxes + for (; toDelete > 0; --toDelete) { + this.boxes.pop().destroy(); + } + // fix entity density (just in case -- we may have uneven entity distribution) + this.updateEntityDensity(this.targetDensity); + } else if (radius > this.radius) { + // Grow case (much simpler) + // logMessage("Setting radius to " + radius + " (grow by " + (radius - this.radius) + ")", COLORS.GREEN); + + // Add entities based on entity density + // var density = this.getEntityDensity(); + var density = this.targetDensity; + var oldArea = Math.PI * this.radius * this.radius; + var n = Math.floor(density * Math.PI * (radius * radius - this.radius * this.radius)); + + if (n > 0) { + // logMessage("Adding " + n + " entities", COLORS.GREEN); + + // Add entities (we use a slightly different algorithm to place them in the area between two concentric circles. + // This is *slightly* less uniform (the reason we're not using this everywhere is entities would be tightly clustered + // at the platform center and become spread out as the radius increases), but the use-case here is just incremental + // radius resizes and the user's not likely to notice the difference). + for (; n > 0; --n) { + var theta = Math.randRange(0.0, Math.PI * 2.0); + var r = Math.randRange(this.radius, radius); + // logMessage("theta = " + theta + ", r = " + r); + var pos = { + x: Math.cos(theta) * r + this.position.x, + y: this.position.y, + z: Math.sin(theta) * r + this.position.y + }; + + // var properties = { position: pos }; + // this.randomizer.randomizeShapeAndColor(properties); + // this.boxes.push(new PlatformComponent(properties)); + this.boxes.push(this.spawnEntity()); + } + } + this.radius = radius; + } + } + DynamicPlatform.prototype.updateHeight = function (height) { + logMessage("Setting platform height to " + height); + this.platformHeight = height; + + // Invalidate current boxes to trigger a rebuild + this.boxes.forEach(function (box) { + box.position.x += this.oldRadius * 100; + }); + // this.update(dt, position, radius); + } + /// Gets a random point within the platform bounds. + /// Should maybe get moved to the RandomAttribModel (would be much cleaner), but this works for now. + DynamicPlatform.prototype.randomPoint = function (position, radius) { + position = position || this.position; + radius = radius !== undefined ? radius : this.radius; + return randomCirclePoint(radius, position); + } + /// Old. The RandomAttribModel replaces this and enables realtime editing of the *****_RANGE params. + // DynamicPlatform.prototype.randomDimensions = function () { + // return { + // x: Math.randRange(WIDTH_RANGE[0], WIDTH_RANGE[1]), + // y: Math.randRange(HEIGHT_RANGE[0], HEIGHT_RANGE[1]), + // z: Math.randRange(DEPTH_RANGE[0], DEPTH_RANGE[1]) + // }; + // } + // DynamicPlatform.prototype.randomColor = function () { + // var shade = Math.randRange(SHADE_RANGE[0], SHADE_RANGE[1]); + // // var h = HUE_RANGE; + // return { + // red: shade + Math.randRange(RED_RANGE[0], RED_RANGE[1]) | 0, + // green: shade + Math.randRange(GREEN_RANGE[0], GREEN_RANGE[1]) | 0, + // blue: shade + Math.randRange(BLUE_RANGE[0], BLUE_RANGE[1]) | 0 + // } + // // return COLORS[Math.randInt(COLORS.length)] + // } + + /// Cleanup. + DynamicPlatform.prototype.destroy = function () { + this.boxes.forEach(function (box) { + box.destroy(); + }); + this.boxes = []; + } +})(); + +// UI +(function () { + var CATCH_SETUP_ERRORS = true; + + // Util functions for setting up widgets (the widget library is intended to be used like this) + function makePanel (dir, properties) { + return new UI.WidgetStack(withDefaults(properties, { + dir: dir + })); + } + function addSpacing (parent, width, height) { + parent.add(new UI.Box({ + backgroundAlpha: 0.0, + width: width, height: height + })); + } + function addLabel (parent, text) { + return parent.add(new UI.Label({ + text: text, + width: 200, + height: 20 + })); + } + function addSlider (parent, label, min, max, getValue, onValueChanged) { + try { + var layout = parent.add(new UI.WidgetStack({ dir: "+x" })); + var textLabel = layout.add(new UI.Label({ + text: label, + width: 130, + height: 20 + })); + var valueLabel = layout.add(new UI.Label({ + text: "" + (+getValue().toFixed(1)), + width: 60, + height: 20 + })); + var slider = layout.add(new UI.Slider({ + value: getValue(), minValue: min, maxValue: max, + width: 300, height: 20, + slider: { + width: 30, + height: 18 + }, + onValueChanged: function (value) { + valueLabel.setText("" + (+value.toFixed(1))); + onValueChanged(value, slider); + UI.updateLayout(); + } + })); + return slider; + } catch (e) { + logMessage("" + e, COLORS.RED); + logMessage("parent: " + parent, COLORS.RED); + logMessage("label: " + label, COLORS.RED); + logMessage("min: " + min, COLORS.RED); + logMessage("max: " + max, COLORS.RED); + logMessage("getValue: " + getValue, COLORS.RED); + logMessage("onValueChanged: " + onValueChanged, COLORS.RED); + throw e; + } + } + function addButton (parent, label, onClicked) { + var button = parent.add(new UI.Box({ + text: label, + width: 160, + height: 26, + leftMargin: 8, + topMargin: 3 + })); + button.addAction('onClick', onClicked); + return button; + } + function moveToBottomLeftScreenCorner (widget) { + var border = 5; + var pos = { + x: border, + y: Controller.getViewportDimensions().y - widget.getHeight() - border + }; + if (widget.position.x != pos.x || widget.position.y != pos.y) { + widget.setPosition(pos.x, pos.y); + UI.updateLayout(); + } + } + var _export = this; + + /// Setup the UI. Creates a bunch of sliders for setting the platform radius, density, and entity color / shape properties. + /// The entityCount slider is readonly. + function _setupUI (platform) { + var layoutContainer = makePanel("+y", { visible: false }); + // layoutContainer.setPosition(10, 280); + // makeDraggable(layoutContainer); + _export.onScreenResize = function () { + moveToBottomLeftScreenCorner(layoutContainer); + } + var topSection = layoutContainer.add(makePanel("+x")); addSpacing(layoutContainer, 1, 5); + var btmSection = layoutContainer.add(makePanel("+x")); + + var controls = topSection.add(makePanel("+y")); addSpacing(topSection, 20, 1); + var buttons = topSection.add(makePanel("+y")); addSpacing(topSection, 20, 1); + + var colorControls = btmSection.add(makePanel("+y")); addSpacing(btmSection, 20, 1); + var shapeControls = btmSection.add(makePanel("+y")); addSpacing(btmSection, 20, 1); + + // Top controls + addLabel(controls, "Platform (platform.js)"); + controls.radiusSlider = addSlider(controls, "radius", PLATFORM_RADIUS_RANGE[0], PLATFORM_RADIUS_RANGE[1], function () { return platform.getRadius() }, + function (value) { + platform.setRadiusOnNextUpdate(value); + controls.entityCountSlider.setValue(platform.getEntityCountWithRadius(value)); + }); + addSpacing(controls, 1, 2); + controls.densitySlider = addSlider(controls, "entity density", PLATFORM_DENSITY_RANGE[0], PLATFORM_DENSITY_RANGE[1], function () { return platform.getEntityDensity() }, + function (value) { + platform.setDensityOnNextUpdate(value); + controls.entityCountSlider.setValue(platform.getEntityCountWithDensity(value)); + }); + addSpacing(controls, 1, 2); + + var minEntities = Math.PI * PLATFORM_RADIUS_RANGE[0] * PLATFORM_RADIUS_RANGE[0] * PLATFORM_DENSITY_RANGE[0]; + var maxEntities = Math.PI * PLATFORM_RADIUS_RANGE[1] * PLATFORM_RADIUS_RANGE[1] * PLATFORM_DENSITY_RANGE[1]; + controls.entityCountSlider = addSlider(controls, "entity count", minEntities, maxEntities, function () { return platform.getEntityCount() }, + function (value) {}); + controls.entityCountSlider.actions = {}; // hack: make this slider readonly (clears all attached actions) + controls.entityCountSlider.slider.actions = {}; + + // Buttons + addSpacing(buttons, 1, 22); + addButton(buttons, 'rebuild', function () { + platform.updateHeight(MyAvatar.position.y - AVATAR_HEIGHT_OFFSET); + }); + addSpacing(buttons, 1, 2); + addButton(buttons, 'toggle entity type', function () { + platform.toggleBoxType(); + }); + + // Bottom controls + + // Iterate over controls (making sliders) for the RNG shape / dimensions model + platform.randomizer.shapeModel.setupUI(function (name, value, min, max, setValue) { + // logMessage("platform.randomizer.shapeModel." + name + " = " + value); + var internal = { + avg: (value[0] + value[1]) * 0.5, + range: Math.abs(value[0] - value[1]) + }; + // logMessage(JSON.stringify(internal), COLORS.GREEN); + addSlider(shapeControls, name + ' avg', min, max, function () { return internal.avg; }, function (value) { + internal.avg = value; + setValue([ internal.avg - internal.range * 0.5, internal.avg + internal.range * 0.5 ]); + platform.updateEntityAttribs(); + }); + addSpacing(shapeControls, 1, 2); + addSlider(shapeControls, name + ' range', min, max, function () { return internal.range }, function (value) { + internal.range = value; + setValue([ internal.avg - internal.range * 0.5, internal.avg + internal.range * 0.5 ]); + platform.updateEntityAttribs(); + }); + addSpacing(shapeControls, 1, 2); + }); + // Do the same for the color model + platform.randomizer.colorModel.setupUI(function (name, value, min, max, setValue) { + // logMessage("platform.randomizer.colorModel." + name + " = " + value); + addSlider(colorControls, name, min, max, function () { return value; }, function (value) { + setValue(value); + platform.updateEntityAttribs(); + }); + addSpacing(colorControls, 1, 2); + }); + + moveToBottomLeftScreenCorner(layoutContainer); + layoutContainer.setVisible(true); + } + this.setupUI = function (platform) { + if (CATCH_SETUP_ERRORS) { + try { + _setupUI(platform); + } catch (e) { + logMessage("Error setting up ui: " + e, COLORS.RED); + } + } else { + _setupUI(platform); + } + } +})(); + +// Error handling w/ explicit try / catch blocks. Good for catching unexpected errors with the onscreen debugLog +// (if it's enabled); bad for detailed debugging since you lose the file and line num even if the error gets rethrown. + +// Catch errors from init +var CATCH_INIT_ERRORS = true; + +// Catch errors from everything (technically, Script and Controller signals that runs platform / ui code) +var CATCH_ERRORS_FROM_EVENT_UPDATES = false; + +// Setup everything +(function () { + var doLater = null; + if (CATCH_ERRORS_FROM_EVENT_UPDATES) { + // Decorates a function w/ explicit error catching + printing to the debug log. + function catchErrors (fcn) { + return function () { + try { + fcn.apply(this, arguments); + } catch (e) { + logMessage('' + e, COLORS.RED); + logMessage("while calling " + fcn); + logMessage("Called by: " + arguments.callee.caller); + } + } + } + // We need to do this after the functions are registered... + doLater = function () { + // Intercept errors from functions called by Script.update and Script.ScriptEnding. + [ 'teardown', 'startup', 'update', 'initPlatform', 'setupUI' ].forEach(function (fcn) { + this[fcn] = catchErrors(this[fcn]); + }); + }; + // These need to be wrapped first though: + + // Intercept errors from UI functions called by Controller.****Event. + [ 'handleMousePress', 'handleMouseMove', 'handleMouseRelease' ].forEach(function (fcn) { + UI[fcn] = catchErrors(UI[fcn]); + }); + } + + function getTargetPlatformPosition () { + var pos = MyAvatar.position; + pos.y -= AVATAR_HEIGHT_OFFSET; + return pos; + } + + // Program state + var platform = this.platform = null; + var lastHeight = null; + + // Init + this.initPlatform = function () { + platform = new DynamicPlatform(NUM_PLATFORM_ENTITIES, getTargetPlatformPosition(), RADIUS); + lastHeight = getTargetPlatformPosition().y; + } + + // Handle relative screen positioning (UI) + var lastDimensions = Controller.getViewportDimensions(); + function checkScreenDimensions () { + var dimensions = Controller.getViewportDimensions(); + if (dimensions.x != lastDimensions.x || dimensions.y != lastDimensions.y) { + onScreenResize(dimensions.x, dimensions.y); + } + lastDimensions = dimensions; + } + + // Update + this.update = function (dt) { + checkScreenDimensions(); + var pos = getTargetPlatformPosition(); + platform.update(dt, getTargetPlatformPosition(), platform.getRadius()); + } + + // Teardown + this.teardown = function () { + try { + platform.destroy(); + UI.teardown(); + + Controller.mousePressEvent.disconnect(UI.handleMousePress); + Controller.mouseMoveEvent.disconnect(UI.handleMouseMove); + Controller.mouseReleaseEvent.disconnect(UI.handleMouseRelease); + } catch (e) { + logMessage("" + e, COLORS.RED); + } + } + + if (doLater) { + doLater(); + } + + // Delays startup until / if entities can be spawned. + this.startup = function (dt) { + if (Entities.canAdjustLocks() && Entities.canRez()) { + Script.update.disconnect(this.startup); + + function init () { + logMessage("initializing..."); + + this.initPlatform(); + + Script.update.connect(this.update); + Script.scriptEnding.connect(this.teardown); + + this.setupUI(platform); + + logMessage("finished initializing.", COLORS.GREEN); + } + if (CATCH_INIT_ERRORS) { + try { + init(); + } catch (error) { + logMessage("" + error, COLORS.RED); + } + } else { + init(); + } + + Controller.mousePressEvent.connect(UI.handleMousePress); + Controller.mouseMoveEvent.connect(UI.handleMouseMove); + Controller.mouseReleaseEvent.connect(UI.handleMouseRelease); + } else { + if (!startup.printedWarnMsg) { + startup.timer = startup.timer || startup.ENTITY_SERVER_WAIT_TIME; + if ((startup.timer -= dt) < 0.0) { + logMessage("Waiting for entity server"); + startup.printedWarnMsg = true; + } + + } + } + } + startup.ENTITY_SERVER_WAIT_TIME = 0.2; // print "waiting for entity server" if more than this time has elapsed in startup() + + Script.update.connect(this.startup); +})(); + +})(); diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index 70eda8e5f2..479acd2ce8 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -28,13 +28,10 @@ if (this.Vec2 == undefined) { return new Vec2(v.x, v.y); } } else if (this.Vec2.clone == undefined) { - print("Vec2 exists; adding Vec2.clone"); this.Vec2.clone = function (v) { return { 'x': v.x || 0.0, 'y': v.y || 0.0 }; } -} else { - print("Vec2...?"); -} +} else {} })(); var Rect = function (xmin, ymin, xmax, ymax) { @@ -566,46 +563,51 @@ var Slider = UI.Slider = function (properties) { this.slider = new Box(properties.slider); this.slider.parent = this; - var updateSliderPos = function (event, widget) { - var rx = Math.max(event.x * 1.0 - widget.position.x - widget.slider.width * 0.5, 0.0); + var clickOffset = { x: 0.0, y: 0.0 }; // offset relative to slider knob + var widget = this; + var updateDrag = function (event) { + var rx = Math.max(event.x * 1.0 - widget.position.x - clickOffset.x, 0.0); var width = Math.max(widget.width - widget.slider.width - widget.padding.x * 2.0, 0.0); var v = Math.min(rx, width) / (width || 1); - widget.value = widget.minValue + ( - widget.maxValue - widget.minValue) * v; + // print("dragging slider: rx = " + rx + ", width = " + width + ", v = " + v); + + widget.value = widget.minValue + (widget.maxValue - widget.minValue) * v; widget.onValueChanged(widget.value); UI.updateLayout(); } + var startDrag = function (event) { + // calculate position of slider knob + var x0 = widget.position.x + widget.padding.x; + var width = (widget.width - widget.slider.width - widget.padding.x * 2.0); + var normalizedValue = (widget.value - widget.minValue) / (widget.maxValue - widget.minValue) - var widget = this; - this.addAction('onMouseDown', function (event) { - sliderRel.x = sliderRel.y = 0.0; - // sliderRel.x = widget.slider.width * 0.5; - // sliderRel.y = widget.slider.height * 0.5; - updateSliderPos(event, widget); + var sliderX = x0 + normalizedValue * width; + var sliderWidth = widget.slider.width; - // hack - ui.clickedWidget = ui.draggedWidget = widget.slider; - }); + if (event.x >= sliderX && event.x <= sliderX + sliderWidth) { + // print("Start drag -- on slider knob"); + clickOffset.x = event.x - sliderX; + } else if (event.x >= x0 && event.x <= x0 + width) { + // print("Start drag -- on slider bar"); + clickOffset.x = sliderWidth * 0.5; + } else { + clickOffset.x = 0.0; + // print("Start drag -- out of bounds!"); + // print("event.x = " + event.x); + // print("x0 = " + x0 + ", x1 = " + (x0 + width) + " (width = " + width + ")"); + // print("s0 = " + sliderX + ", s1 = " + (sliderX + sliderWidth) + "(slider width = " + sliderWidth + ")"); + // print("widget = " + widget); + // print("widget.slider = " + widget.slider); + // print("widget.width = " + widget.width + ", widget.slider.width = " + widget.slider.width); + } + updateDrag(event); + } - var sliderRel = {}; - this.slider.addAction('onMouseDown', function (event) { - sliderRel.x = widget.slider.position.x - event.x; - sliderRel.y = widget.slider.position.y - event.y; - event.x += sliderRel.x; - event.y += sliderRel.y; - updateSliderPos(event, widget); - }); - this.slider.addAction('onDragBegin', function (event) { - event.x += sliderRel.x; - event.y += sliderRel.y; - updateSliderPos(event, widget); - }) - this.slider.addAction('onDragUpdate', function (event) { - event.x += sliderRel.x; - event.y += sliderRel.y; - updateSliderPos(event, widget); - }) + this.addAction('onMouseDown', startDrag); + this.addAction('onDragBegin', updateDrag); + this.addAction('onDragUpdate', updateDrag); + this.slider.actions = this.actions; }; Slider.prototype = new Box(); Slider.prototype.constructor = Slider; @@ -947,16 +949,25 @@ var dispatchEvent = function (action, event, widget) { } } +function hasAction (widget, action) { + // print("widget = " + widget); + // print("action = " + action); + // if (widget) { + // print("widget.actions[] = " + widget.actions[action]); + // print("widget.parent = " + widget.parent); + // } + return widget && (widget.actions[action] || hasAction(widget.parent, action)); +} + UI.handleMouseMove = function (event, canStartDrag) { - if (canStartDrag === undefined) + // if (canStartDrag === undefined) + if (arguments.length < 2) canStartDrag = true; // print("mouse moved x = " + event.x + ", y = " + event.y); var focused = getFocusedWidget(event); - // print("got focus: " + focused); - - if (canStartDrag && !ui.draggedWidget && ui.clickedWidget && ui.clickedWidget.actions['onDragBegin']) { + if (!ui.draggedWidget && ui.clickedWidget && hasAction(ui.clickedWidget, 'onDragBegin')) { ui.draggedWidget = ui.clickedWidget; dispatchEvent('onDragBegin', event, ui.draggedWidget); } else if (ui.draggedWidget) { @@ -980,26 +991,24 @@ UI.handleMousePress = function (event) { } UI.handleMouseDoublePress = function (event) { - // print("DOUBLE CLICK!"); var focused = getFocusedWidget(event); UI.handleMouseMove(event); if (focused) { - // print("dispatched onDoubleClick"); dispatchEvent('onDoubleClick', event, focused); } } UI.handleMouseRelease = function (event) { - // print("Mouse released"); - if (ui.draggedWidget) { dispatchEvent('onDragEnd', event, ui.draggedWidget); } else { - UI.handleMouseMove(event, false); + var clicked = ui.clickedWidget; + ui.clickedWidget = null; + UI.handleMouseMove(event); if (ui.focusedWidget) { dispatchEvent('onMouseUp', event, ui.focusedWidget); - if (ui.clickedWidget == ui.focusedWidget) { + if (clicked == ui.focusedWidget) { dispatchEvent('onClick', event, ui.focusedWidget); } } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index c3829c7be2..eb39fbc70f 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -168,7 +168,7 @@ Item { color: root.fontColor; font.pixelSize: root.fontSize text: "Triangles: " + root.triangles + - " / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches + " / Material Switches: " + root.materialSwitches } Text { color: root.fontColor; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 36b56bfa07..1bc6e0d3ba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1151,14 +1151,27 @@ void Application::paintGL() { } } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); - _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + - glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0) + - (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * - glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); + if (isHMDMode()) { + glm::quat hmdRotation = extractRotation(_myAvatar->getHMDSensorMatrix()); + _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() + * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)) * hmdRotation); + glm::vec3 hmdOffset = extractTranslation(_myAvatar->getHMDSensorMatrix()); + _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0) + + (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * + glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror + + (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))) * hmdOffset); + } else { + _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation() + * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f))); + _myCamera.setPosition(_myAvatar->getDefaultEyePosition() + + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0) + + (_myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) * + glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror); + } renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; } - // Update camera position + // Update camera position if (!isHMDMode()) { _myCamera.update(1.0f / _fps); } @@ -2600,11 +2613,7 @@ void Application::updateMyAvatarLookAtPosition() { if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { // When I am in mirror mode, just look right at the camera (myself); don't switch gaze points because when physically // looking in a mirror one's eyes appear steady. - if (!isHMD) { - lookAtSpot = _myCamera.getPosition(); - } else { - lookAtSpot = _myCamera.getPosition() + transformPoint(_myAvatar->getSensorToWorldMatrix(), extractTranslation(getHMDSensorPose())); - } + lookAtSpot = _myCamera.getPosition(); } else if (eyeTracker->isTracking() && (isHMD || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. if (isHMD) { diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index e2b64869ec..4af1a26612 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -206,7 +206,7 @@ void Stars::render(RenderArgs* renderArgs, float alpha) { float msecs = (float)(usecTimestampNow() - start) / (float)USECS_PER_MSEC; float secs = msecs / (float)MSECS_PER_SECOND; batch._glUniform1f(_timeSlot, secs); - geometryCache->renderUnitCube(batch); + geometryCache->renderCube(batch); static const size_t VERTEX_STRIDE = sizeof(StarVertex); size_t offset = offsetof(StarVertex, position); diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index dad34e9243..d09dd41999 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "world.h" #include "Application.h" @@ -93,29 +94,28 @@ void renderWorldBox(gpu::Batch& batch) { geometryCache->renderLine(batch, glm::vec3(-HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), glm::vec3(HALF_TREE_SCALE, 0.0f, HALF_TREE_SCALE), GREY); - geometryCache->renderWireCube(batch, TREE_SCALE, GREY4); + auto deferredLighting = DependencyManager::get(); + + deferredLighting->renderWireCubeInstance(batch, Transform(), GREY4); // Draw meter markers along the 3 axis to help with measuring things const float MARKER_DISTANCE = 1.0f; const float MARKER_RADIUS = 0.05f; - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, RED); + transform = Transform().setScale(MARKER_RADIUS); + deferredLighting->renderSolidSphereInstance(batch, transform, RED); - transform.setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, 0.0f)); - batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, RED); + transform = Transform().setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, 0.0f)).setScale(MARKER_RADIUS); + deferredLighting->renderSolidSphereInstance(batch, transform, RED); - transform.setTranslation(glm::vec3(0.0f, MARKER_DISTANCE, 0.0f)); - batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, GREEN); + transform = Transform().setTranslation(glm::vec3(0.0f, MARKER_DISTANCE, 0.0f)).setScale(MARKER_RADIUS); + deferredLighting->renderSolidSphereInstance(batch, transform, GREEN); - transform.setTranslation(glm::vec3(0.0f, 0.0f, MARKER_DISTANCE)); - batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, BLUE); + transform = Transform().setTranslation(glm::vec3(0.0f, 0.0f, MARKER_DISTANCE)).setScale(MARKER_RADIUS); + deferredLighting->renderSolidSphereInstance(batch, transform, BLUE); - transform.setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, MARKER_DISTANCE)); - batch.setModelTransform(transform); - geometryCache->renderSphere(batch, MARKER_RADIUS, 10, 10, GREY); + transform = Transform().setTranslation(glm::vec3(MARKER_DISTANCE, 0.0f, MARKER_DISTANCE)).setScale(MARKER_RADIUS); + deferredLighting->renderSolidSphereInstance(batch, transform, GREY); } // Return a random vector of average length 1 diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e6bcca1f87..0d1c3b8a20 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -448,15 +448,14 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { // If this is the avatar being looked at, render a little ball above their head if (_isLookAtTarget && Menu::getInstance()->isOptionChecked(MenuOption::RenderFocusIndicator)) { - const float INDICATOR_OFFSET = 0.22f; - const float INDICATOR_RADIUS = 0.03f; - const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; + static const float INDICATOR_OFFSET = 0.22f; + static const float INDICATOR_RADIUS = 0.03f; + static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; glm::vec3 position = glm::vec3(_position.x, getDisplayNamePosition().y + INDICATOR_OFFSET, _position.z); Transform transform; transform.setTranslation(position); - batch.setModelTransform(transform); - DependencyManager::get()->renderSolidSphere(batch, INDICATOR_RADIUS, - 15, 15, LOOK_AT_INDICATOR_COLOR); + transform.postScale(INDICATOR_RADIUS); + DependencyManager::get()->renderSolidSphereInstance(batch, transform, LOOK_AT_INDICATOR_COLOR); } // If the avatar is looking at me, indicate that they are @@ -473,27 +472,29 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (geometry && geometry->isLoaded()) { const float DEFAULT_EYE_DIAMETER = 0.048f; // Typical human eye const float RADIUS_INCREMENT = 0.005f; - Transform transform; + batch.setModelTransform(Transform()); glm::vec3 position = getHead()->getLeftEyePosition(); + Transform transform; transform.setTranslation(position); - batch.setModelTransform(transform); float eyeDiameter = geometry->getFBXGeometry().leftEyeSize; if (eyeDiameter == 0.0f) { eyeDiameter = DEFAULT_EYE_DIAMETER; } - DependencyManager::get()->renderSolidSphere(batch, - eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT, 15, 15, glm::vec4(LOOKING_AT_ME_COLOR, alpha)); + + DependencyManager::get()->renderSolidSphereInstance(batch, + Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT), + glm::vec4(LOOKING_AT_ME_COLOR, alpha)); position = getHead()->getRightEyePosition(); transform.setTranslation(position); - batch.setModelTransform(transform); eyeDiameter = geometry->getFBXGeometry().rightEyeSize; if (eyeDiameter == 0.0f) { eyeDiameter = DEFAULT_EYE_DIAMETER; } - DependencyManager::get()->renderSolidSphere(batch, - eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT, 15, 15, glm::vec4(LOOKING_AT_ME_COLOR, alpha)); + DependencyManager::get()->renderSolidSphereInstance(batch, + Transform(transform).postScale(eyeDiameter * _scale / 2.0f + RADIUS_INCREMENT), + glm::vec4(LOOKING_AT_ME_COLOR, alpha)); } } @@ -518,19 +519,16 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { if (renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { + batch.setModelTransform(Transform()); + + Transform transform; transform.setTranslation(_position); transform.setScale(height); - batch.setModelTransform(transform); - - if (_voiceSphereID == GeometryCache::UNKNOWN_ID) { - _voiceSphereID = DependencyManager::get()->allocateID(); - } - - DependencyManager::get()->bindSimpleProgram(batch); - DependencyManager::get()->renderSphere(batch, sphereRadius, 15, 15, - glm::vec4(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE), true, - _voiceSphereID); + transform.postScale(sphereRadius); + DependencyManager::get()->renderSolidSphereInstance(batch, + transform, + glm::vec4(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE)); } } } diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index a0125ca736..a144661f8b 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "Avatar.h" #include "AvatarManager.h" @@ -65,16 +66,16 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) { Transform transform = Transform(); transform.setTranslation(position); transform.setRotation(palm.getRotation()); - batch.setModelTransform(transform); - DependencyManager::get()->renderSphere(batch, SPHERE_RADIUS, - NUM_FACETS, NUM_FACETS, grayColor); + transform.postScale(SPHERE_RADIUS); + DependencyManager::get()->renderSolidSphereInstance(batch, transform, grayColor); // draw a green sphere at the old "finger tip" + transform = Transform(); position = palm.getTipPosition(); transform.setTranslation(position); - batch.setModelTransform(transform); - DependencyManager::get()->renderSphere(batch, SPHERE_RADIUS, - NUM_FACETS, NUM_FACETS, greenColor, false); + transform.setRotation(palm.getRotation()); + transform.postScale(SPHERE_RADIUS); + DependencyManager::get()->renderSolidSphereInstance(batch, transform, greenColor); } } diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index a514eb4e8d..96c55dfa93 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -462,13 +462,10 @@ void Head::renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition) auto& batch = *renderArgs->_batch; auto transform = Transform{}; transform.setTranslation(lookatPosition); - batch.setModelTransform(transform); auto deferredLighting = DependencyManager::get(); - deferredLighting->bindSimpleProgram(batch); - - auto geometryCache = DependencyManager::get(); const float LOOK_AT_TARGET_RADIUS = 0.075f; + transform.postScale(LOOK_AT_TARGET_RADIUS); const glm::vec4 LOOK_AT_TARGET_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; - geometryCache->renderSphere(batch, LOOK_AT_TARGET_RADIUS, 15, 15, LOOK_AT_TARGET_COLOR, true); + deferredLighting->renderSolidSphereInstance(batch, transform, LOOK_AT_TARGET_COLOR); } diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 27e61175eb..6b56e92d80 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -639,27 +639,25 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha auto geometryCache = DependencyManager::get(); auto deferredLighting = DependencyManager::get(); - Transform transform; // = Transform(); - // draw a blue sphere at the capsule top point glm::vec3 topPoint = _translation + _boundingCapsuleLocalOffset + (0.5f * _boundingCapsuleHeight) * glm::vec3(0.0f, 1.0f, 0.0f); - transform.setTranslation(topPoint); - batch.setModelTransform(transform); - deferredLighting->bindSimpleProgram(batch); - geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS, - glm::vec4(0.6f, 0.6f, 0.8f, alpha)); + + deferredLighting->renderSolidSphereInstance(batch, + Transform().setTranslation(topPoint).postScale(_boundingCapsuleRadius), + glm::vec4(0.6f, 0.6f, 0.8f, alpha)); // draw a yellow sphere at the capsule bottom point glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f); glm::vec3 axis = topPoint - bottomPoint; - transform.setTranslation(bottomPoint); - batch.setModelTransform(transform); - deferredLighting->bindSimpleProgram(batch); - geometryCache->renderSphere(batch, _boundingCapsuleRadius, BALL_SUBDIVISIONS, BALL_SUBDIVISIONS, - glm::vec4(0.8f, 0.8f, 0.6f, alpha)); + + deferredLighting->renderSolidSphereInstance(batch, + Transform().setTranslation(bottomPoint).postScale(_boundingCapsuleRadius), + glm::vec4(0.8f, 0.8f, 0.6f, alpha)); // draw a green cylinder between the two points glm::vec3 origin(0.0f); + batch.setModelTransform(Transform().setTranslation(bottomPoint)); + deferredLighting->bindSimpleProgram(batch); Avatar::renderJointConnectingCone(batch, origin, axis, _boundingCapsuleRadius, _boundingCapsuleRadius, glm::vec4(0.6f, 0.8f, 0.6f, alpha)); } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8ddb767537..4dd552210c 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -337,7 +337,6 @@ void Stats::updateStats() { void Stats::setRenderDetails(const RenderDetails& details) { STAT_UPDATE(triangles, details._trianglesRendered); - STAT_UPDATE(quads, details._quadsRendered); STAT_UPDATE(materialSwitches, details._materialSwitches); if (_expanded) { STAT_UPDATE(meshOpaque, details._opaque._rendered); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index a306c7c86d..3ad887ca65 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -61,8 +61,7 @@ void Cube3DOverlay::render(RenderArgs* args) { // } transform.setScale(dimensions); - batch->setModelTransform(transform); - DependencyManager::get()->renderSolidCube(*batch, 1.0f, cubeColor); + DependencyManager::get()->renderSolidCubeInstance(*batch, transform, cubeColor); } else { if (getIsDashedLine()) { @@ -98,9 +97,9 @@ void Cube3DOverlay::render(RenderArgs* args) { geometryCache->renderDashedLine(*batch, bottomRightFar, topRightFar, cubeColor); } else { + batch->setModelTransform(Transform()); transform.setScale(dimensions); - batch->setModelTransform(transform); - DependencyManager::get()->renderWireCube(*batch, 1.0f, cubeColor); + DependencyManager::get()->renderWireCubeInstance(*batch, transform, cubeColor); } } } diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 9712375209..0df09d25f6 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -12,11 +12,16 @@ #include #include +#include #include #include QString const Sphere3DOverlay::TYPE = "sphere"; +// Sphere overlays should fit inside a cube of the specified dimensions, hence it needs to be a half unit sphere. +// However, the geometry cache renders a UNIT sphere, so we need to scale down. +static const float SPHERE_OVERLAY_SCALE = 0.5f; + Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : Volume3DOverlay(Sphere3DOverlay) { @@ -36,10 +41,15 @@ void Sphere3DOverlay::render(RenderArgs* args) { auto batch = args->_batch; if (batch) { + batch->setModelTransform(Transform()); + Transform transform = _transform; - transform.postScale(getDimensions()); - batch->setModelTransform(transform); - DependencyManager::get()->renderSphere(*batch, 1.0f, SLICES, SLICES, sphereColor, _isSolid); + transform.postScale(getDimensions() * SPHERE_OVERLAY_SCALE); + if (_isSolid) { + DependencyManager::get()->renderSolidSphereInstance(*batch, transform, sphereColor); + } else { + DependencyManager::get()->renderWireSphereInstance(*batch, transform, sphereColor); + } } } diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index b9ff69af52..187b25e75a 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -57,7 +57,9 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { if (_procedural->ready()) { batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well _procedural->prepare(batch, this->getDimensions()); - DependencyManager::get()->renderSolidCube(batch, 1.0f, _procedural->getColor(cubeColor)); + auto color = _procedural->getColor(cubeColor); + batch._glColor4f(color.r, color.g, color.b, color.a); + DependencyManager::get()->renderCube(batch); } else { DependencyManager::get()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor); } diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp index 2770c4edc2..a13ed5b06d 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -23,8 +23,13 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render float puffedOut, glm::vec4& color) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(entity->getTransformToCenter()); // we want to include the scale as well - DependencyManager::get()->renderWireCube(batch, 1.0f + puffedOut, color); + + auto shapeTransform = entity->getTransformToCenter(); + if (puffedOut != 0.0) { + shapeTransform.postScale(1.0 + puffedOut); + } + batch.setModelTransform(Transform()); // we want to include the scale as well + DependencyManager::get()->renderWireCubeInstance(batch, shapeTransform, color); } void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) { diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 82257c67fb..3cfc18046a 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -24,6 +24,11 @@ #include "../render-utils/simple_vert.h" #include "../render-utils/simple_frag.h" +// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 +// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. +static const float SPHERE_ENTITY_SCALE = 0.5f; + + EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); } @@ -39,15 +44,7 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableSphereEntityItem::render"); Q_ASSERT(getType() == EntityTypes::Sphere); Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); // use a transform with scale, rotation, registration point and translation - // TODO: it would be cool to select different slices/stacks geometry based on the size of the sphere - // and the distance to the viewer. This would allow us to reduce the triangle count for smaller spheres - // that aren't close enough to see the tessellation and use larger triangle count for spheres that would - // expose that effect - static const int SLICES = 15, STACKS = 15; - if (!_procedural) { _procedural.reset(new Procedural(getUserData())); _procedural->_vertexSource = simple_vert; @@ -59,12 +56,19 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } + gpu::Batch& batch = *args->_batch; glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha()); + Transform modelTransform = getTransformToCenter(); + modelTransform.postScale(SPHERE_ENTITY_SCALE); if (_procedural->ready()) { + batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation _procedural->prepare(batch, getDimensions()); - DependencyManager::get()->renderSphere(batch, 0.5f, SLICES, STACKS, _procedural->getColor(sphereColor)); + auto color = _procedural->getColor(sphereColor); + batch._glColor4f(color.r, color.g, color.b, color.a); + DependencyManager::get()->renderSphere(batch); } else { - DependencyManager::get()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); + batch.setModelTransform(Transform()); + DependencyManager::get()->renderSolidSphereInstance(batch, modelTransform, sphereColor); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 930a684617..c8088b7406 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -19,6 +19,10 @@ #include #include +// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 +// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. +static const float SPHERE_ENTITY_SCALE = 0.5f; + EntityItemPointer RenderableZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); } @@ -121,15 +125,15 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); - + batch.setModelTransform(Transform()); + + auto shapeTransform = getTransformToCenter(); auto deferredLightingEffect = DependencyManager::get(); - if (getShapeType() == SHAPE_TYPE_SPHERE) { - const int SLICES = 15, STACKS = 15; - deferredLightingEffect->renderWireSphere(batch, 0.5f, SLICES, STACKS, DEFAULT_COLOR); + shapeTransform.postScale(SPHERE_ENTITY_SCALE); + deferredLightingEffect->renderWireSphereInstance(batch, shapeTransform, DEFAULT_COLOR); } else { - deferredLightingEffect->renderWireCube(batch, 1.0f, DEFAULT_COLOR); + deferredLightingEffect->renderWireCubeInstance(batch, shapeTransform, DEFAULT_COLOR); } break; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 65b41b5734..13138e5c10 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -498,18 +498,21 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Models only if (_type == EntityTypes::Model) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_URL, modelURL); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, animationURL); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); } - if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); } + // FIXME - it seems like ParticleEffect should also support this + if (_type == EntityTypes::Model || _type == EntityTypes::Zone) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); + } + // Models & Particles if (_type == EntityTypes::Model || _type == EntityTypes::ParticleEffect) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_PLAYING, animationIsPlaying); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, animationFPS); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex); diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 50d27ea283..91d2739887 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -265,24 +265,30 @@ void appendModelIDs(const QString& parentID, const QMultiHash& } -gpu::BufferPointer FBXMeshPart::getTrianglesForQuads() const { +gpu::BufferPointer FBXMeshPart::getMergedTriangles() const { // if we've been asked for our triangulation of the original quads, but we don't yet have them // then create them now. - if (!trianglesForQuadsAvailable) { - trianglesForQuadsAvailable = true; + if (!mergedTrianglesAvailable) { + mergedTrianglesAvailable = true; - quadsAsTrianglesIndicesBuffer = std::make_shared(); + mergedTrianglesIndicesBuffer = std::make_shared(); // QVector quadIndices; // original indices from the FBX mesh - QVector quadsAsTrianglesIndices; // triangle versions of quads converted when first needed + QVector mergedTrianglesIndices; // triangle versions of quads converted when first needed + const int INDICES_PER_ORIGINAL_TRIANGLE = 3; const int INDICES_PER_ORIGINAL_QUAD = 4; const int INDICES_PER_TRIANGULATED_QUAD = 6; int numberOfQuads = quadIndices.size() / INDICES_PER_ORIGINAL_QUAD; - - quadsAsTrianglesIndices.resize(numberOfQuads * INDICES_PER_TRIANGULATED_QUAD); + int numberOfTriangles = triangleIndices.size() / INDICES_PER_ORIGINAL_TRIANGLE; + int mergedNumberOfIndices = (numberOfQuads * INDICES_PER_TRIANGULATED_QUAD) + triangleIndices.size(); + + // resized our merged indices to be enough room for our triangulated quads and our original triangles + mergedTrianglesIndices.resize(mergedNumberOfIndices); int originalIndex = 0; int triangulatedIndex = 0; + + // triangulate our quads for (int fromQuad = 0; fromQuad < numberOfQuads; fromQuad++) { int i0 = quadIndices[originalIndex + 0]; int i1 = quadIndices[originalIndex + 1]; @@ -296,23 +302,38 @@ gpu::BufferPointer FBXMeshPart::getTrianglesForQuads() const { // Triangle tri1 = { v0, v1, v2 }; // Triangle tri2 = { v2, v3, v0 }; - quadsAsTrianglesIndices[triangulatedIndex + 0] = i0; - quadsAsTrianglesIndices[triangulatedIndex + 1] = i1; - quadsAsTrianglesIndices[triangulatedIndex + 2] = i3; + mergedTrianglesIndices[triangulatedIndex + 0] = i0; + mergedTrianglesIndices[triangulatedIndex + 1] = i1; + mergedTrianglesIndices[triangulatedIndex + 2] = i3; - quadsAsTrianglesIndices[triangulatedIndex + 3] = i1; - quadsAsTrianglesIndices[triangulatedIndex + 4] = i2; - quadsAsTrianglesIndices[triangulatedIndex + 5] = i3; + mergedTrianglesIndices[triangulatedIndex + 3] = i1; + mergedTrianglesIndices[triangulatedIndex + 4] = i2; + mergedTrianglesIndices[triangulatedIndex + 5] = i3; originalIndex += INDICES_PER_ORIGINAL_QUAD; triangulatedIndex += INDICES_PER_TRIANGULATED_QUAD; } - trianglesForQuadsIndicesCount = INDICES_PER_TRIANGULATED_QUAD * numberOfQuads; - quadsAsTrianglesIndicesBuffer->append(quadsAsTrianglesIndices.size() * sizeof(quint32), (gpu::Byte*)quadsAsTrianglesIndices.data()); + // add our original triangs + originalIndex = 0; + for (int fromTriangle = 0; fromTriangle < numberOfTriangles; fromTriangle++) { + int i0 = triangleIndices[originalIndex + 0]; + int i1 = triangleIndices[originalIndex + 1]; + int i2 = triangleIndices[originalIndex + 2]; + + mergedTrianglesIndices[triangulatedIndex + 0] = i0; + mergedTrianglesIndices[triangulatedIndex + 1] = i1; + mergedTrianglesIndices[triangulatedIndex + 2] = i2; + + originalIndex += INDICES_PER_ORIGINAL_TRIANGLE; + triangulatedIndex += INDICES_PER_ORIGINAL_TRIANGLE; + } + + mergedTrianglesIndicesCount = mergedNumberOfIndices; + mergedTrianglesIndicesBuffer->append(mergedNumberOfIndices * sizeof(quint32), (gpu::Byte*)mergedTrianglesIndices.data()); } - return quadsAsTrianglesIndicesBuffer; + return mergedTrianglesIndicesBuffer; } FBXBlendshape extractBlendshape(const FBXNode& object) { @@ -479,8 +500,6 @@ FBXLight extractLight(const FBXNode& object) { return light; } - - QByteArray fileOnUrl(const QByteArray& filenameString, const QString& url) { QString path = QFileInfo(url).path(); QByteArray filename = filenameString; diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index ddcff8224b..3027eb52cc 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -121,17 +121,17 @@ public: /// A single part of a mesh (with the same material). class FBXMeshPart { public: - + QVector quadIndices; // original indices from the FBX mesh QVector triangleIndices; // original indices from the FBX mesh - mutable gpu::BufferPointer quadsAsTrianglesIndicesBuffer; + mutable gpu::BufferPointer mergedTrianglesIndicesBuffer; // both the quads and the triangles merged into a single set of triangles QString materialID; - mutable bool trianglesForQuadsAvailable = false; - mutable int trianglesForQuadsIndicesCount = 0; + mutable bool mergedTrianglesAvailable = false; + mutable int mergedTrianglesIndicesCount = 0; - gpu::BufferPointer getTrianglesForQuads() const; + gpu::BufferPointer getMergedTriangles() const; }; class FBXMaterial { @@ -153,7 +153,6 @@ public: FBXTexture emissiveTexture; bool needTangentSpace() const; - }; /// A single mesh (with optional blendshapes) extracted from an FBX document. diff --git a/libraries/fbx/src/FBXReader_Mesh.cpp b/libraries/fbx/src/FBXReader_Mesh.cpp index ced917f838..c0cf57df58 100644 --- a/libraries/fbx/src/FBXReader_Mesh.cpp +++ b/libraries/fbx/src/FBXReader_Mesh.cpp @@ -441,12 +441,12 @@ void FBXReader::buildModelMesh(ExtractedMesh& extracted, const QString& url) { if (clusterIndicesSize) { mesh.addAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, model::BufferView(attribBuffer, clusterIndicesOffset, clusterIndicesSize, - gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW))); + gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW))); } if (clusterWeightsSize) { mesh.addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, model::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize, - gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW))); + gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW))); } diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index e5ec8525b6..e6e176be88 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -304,12 +304,16 @@ bool Batch::isSkyboxEnabled() const { return _enableSkybox; } -void Batch::setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function) { +void Batch::setupNamedCalls(const std::string& instanceName, size_t count, NamedBatchData::Function function) { NamedBatchData& instance = _namedData[instanceName]; - ++instance._count; + instance._count += count; instance._function = function; } +void Batch::setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function) { + setupNamedCalls(instanceName, 1, function); +} + BufferPointer Batch::getNamedBuffer(const std::string& instanceName, uint8_t index) { NamedBatchData& instance = _namedData[instanceName]; if (instance._buffers.size() <= index) { diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index c3bf6250c5..ec6fb26c34 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -62,8 +62,10 @@ public: Function _function; void process(Batch& batch) { + if (_function) { _function(batch, *this); } + } }; using NamedBatchDataMap = std::map; @@ -96,6 +98,7 @@ public: void drawIndexedInstanced(uint32 nbInstances, Primitive primitiveType, uint32 nbIndices, uint32 startIndex = 0, uint32 startInstance = 0); + void setupNamedCalls(const std::string& instanceName, size_t count, NamedBatchData::Function function); void setupNamedCalls(const std::string& instanceName, NamedBatchData::Function function); BufferPointer getNamedBuffer(const std::string& instanceName, uint8_t index = 0); diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp new file mode 100644 index 0000000000..a66fc19458 --- /dev/null +++ b/libraries/gpu/src/gpu/Format.cpp @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis on 2015/09/20 +// Copyright 2013-2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Format.h" + +using namespace gpu; + +const Element Element::COLOR_RGBA_32{ VEC4, NUINT8, RGBA }; +const Element Element::COLOR_RGBA{ VEC4, FLOAT, RGBA }; +const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; +const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; +const Element Element::VEC3F_XYZ{ VEC3, FLOAT, XYZ }; +const Element Element::VEC4F_XYZW{ VEC4, FLOAT, XYZW }; +const Element Element::INDEX_UINT16{ SCALAR, UINT16, INDEX }; +const Element Element::PART_DRAWCALL{ VEC4, UINT32, PART }; + diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index e16256574b..530db084a3 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -56,10 +56,8 @@ enum Type { INT8, UINT8, - NFLOAT, NINT32, NUINT32, - NHALF, NINT16, NUINT16, NINT8, @@ -68,6 +66,7 @@ enum Type { NUM_TYPES, BOOL = UINT8, + NORMALIZED_START = NINT32, }; // Array providing the size in bytes for a given scalar type static const int TYPE_SIZE[NUM_TYPES] = { @@ -79,10 +78,10 @@ static const int TYPE_SIZE[NUM_TYPES] = { 2, 1, 1, + + // normalized values 4, 4, - 4, - 2, 2, 2, 1, @@ -99,10 +98,9 @@ static const bool TYPE_IS_INTEGER[NUM_TYPES] = { true, true, - false, + // Normalized values true, true, - false, true, true, true, @@ -151,6 +149,7 @@ enum Semantic { RGB, RGBA, BGRA, + XY, XYZ, XYZW, QUAT, @@ -199,7 +198,7 @@ public: uint8 getLocationCount() const { return LOCATION_COUNT[(Dimension)_dimension]; } Type getType() const { return (Type)_type; } - bool isNormalized() const { return (getType() >= NFLOAT); } + bool isNormalized() const { return (getType() >= NORMALIZED_START); } bool isInteger() const { return TYPE_IS_INTEGER[getType()]; } uint32 getSize() const { return DIMENSION_COUNT[_dimension] * TYPE_SIZE[_type]; } @@ -215,10 +214,14 @@ public: } static const Element COLOR_RGBA_32; + static const Element COLOR_RGBA; + static const Element VEC2F_UV; + static const Element VEC2F_XY; static const Element VEC3F_XYZ; + static const Element VEC4F_XYZW; static const Element INDEX_UINT16; static const Element PART_DRAWCALL; - + protected: uint8 _semantic; uint8 _dimension : 4; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 43c01d0337..62508f273c 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -127,7 +127,12 @@ void GLBackend::renderPassTransfer(Batch& batch) { const size_t numCommands = batch.getCommands().size(); const Batch::Commands::value_type* command = batch.getCommands().data(); const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data(); - + + for (auto& cached : batch._buffers._items) { + if (cached._data) { + syncGPUObject(*cached._data); + } + } // Reset the transform buffers _transform._cameras.resize(0); _transform._cameraOffsets.clear(); @@ -330,7 +335,7 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, uint32 paramOffset) { uint32 startInstance = batch._params[paramOffset + 0]._uint; GLenum glType = _elementTypeToGLType[_input._indexBufferType]; - glDrawElementsInstanced(mode, numIndices, glType, nullptr, numInstances); + glDrawElementsInstanced(mode, numIndices, glType, reinterpret_cast(startIndex + _input._indexBufferOffset), numInstances); (void)CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/GLBackendShared.h b/libraries/gpu/src/gpu/GLBackendShared.h index 7ce54665be..21bd10a33a 100644 --- a/libraries/gpu/src/gpu/GLBackendShared.h +++ b/libraries/gpu/src/gpu/GLBackendShared.h @@ -34,10 +34,9 @@ static const GLenum _elementTypeToGLType[gpu::NUM_TYPES] = { GL_UNSIGNED_SHORT, GL_BYTE, GL_UNSIGNED_BYTE, - GL_FLOAT, + // Normalized values GL_INT, GL_UNSIGNED_INT, - GL_HALF_FLOAT, GL_SHORT, GL_UNSIGNED_SHORT, GL_BYTE, diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index d4e1db125d..dce5236868 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -156,7 +156,6 @@ public: texel.internalFormat = GL_DEPTH_COMPONENT32; break; } - case gpu::NFLOAT: case gpu::FLOAT: { texel.internalFormat = GL_DEPTH_COMPONENT32F; break; @@ -165,8 +164,7 @@ public: case gpu::INT16: case gpu::NUINT16: case gpu::NINT16: - case gpu::HALF: - case gpu::NHALF: { + case gpu::HALF: { texel.internalFormat = GL_DEPTH_COMPONENT16; break; } diff --git a/libraries/gpu/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp index 4cdbe8203a..c162b38b93 100644 --- a/libraries/gpu/src/gpu/Resource.cpp +++ b/libraries/gpu/src/gpu/Resource.cpp @@ -14,11 +14,6 @@ using namespace gpu; -const Element Element::COLOR_RGBA_32 = Element(VEC4, NUINT8, RGBA); -const Element Element::VEC3F_XYZ = Element(VEC3, FLOAT, XYZ); -const Element Element::INDEX_UINT16 = Element(SCALAR, UINT16, INDEX); -const Element Element::PART_DRAWCALL = Element(VEC4, UINT32, PART); - Resource::Size Resource::Sysmem::allocateMemory(Byte** dataAllocated, Size size) { if ( !dataAllocated ) { qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer."; diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index de5e4a7242..f330b0fd07 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -144,6 +144,11 @@ public: return append(sizeof(t), reinterpret_cast(&t)); } + template + Size append(const std::vector& t) { + return append(sizeof(T) * t.size(), reinterpret_cast(&t[0])); + } + // Access the sysmem object. const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); } Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); } diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index 634545b4dd..98d23ac266 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -15,6 +15,37 @@ using namespace gpu; +using ElementArray = std::array; + +const ElementArray& getDefaultElements() { + static ElementArray defaultElements{ + //POSITION = 0, + Element::VEC3F_XYZ, + //NORMAL = 1, + Element::VEC3F_XYZ, + //COLOR = 2, + Element::COLOR_RGBA_32, + //TEXCOORD0 = 3, + Element::VEC2F_UV, + //TANGENT = 4, + Element::VEC3F_XYZ, + //SKIN_CLUSTER_INDEX = 5, + Element::VEC4F_XYZW, + //SKIN_CLUSTER_WEIGHT = 6, + Element::VEC4F_XYZW, + //TEXCOORD1 = 7, + Element::VEC2F_UV, + //INSTANCE_SCALE = 8, + Element::VEC3F_XYZ, + //INSTANCE_TRANSLATE = 9, + Element::VEC3F_XYZ, + //INSTANCE_XFM = 10, + // FIXME make a matrix element + Element::VEC4F_XYZW + }; + return defaultElements; +} + void Stream::Format::evaluateCache() { _channels.clear(); _elementTotalSize = 0; @@ -34,6 +65,19 @@ bool Stream::Format::setAttribute(Slot slot, Slot channel, Element element, Offs return true; } +bool Stream::Format::setAttribute(Slot slot, Frequency frequency) { + _attributes[slot] = Attribute((InputSlot)slot, slot, getDefaultElements()[slot], 0, frequency); + evaluateCache(); + return true; +} + +bool Stream::Format::setAttribute(Slot slot, Slot channel, Frequency frequency) { + _attributes[slot] = Attribute((InputSlot)slot, channel, getDefaultElements()[slot], 0, frequency); + evaluateCache(); + return true; +} + + BufferStream::BufferStream() : _buffers(), _offsets(), diff --git a/libraries/gpu/src/gpu/Stream.h b/libraries/gpu/src/gpu/Stream.h index c0ad1ebe46..420aa50f72 100644 --- a/libraries/gpu/src/gpu/Stream.h +++ b/libraries/gpu/src/gpu/Stream.h @@ -11,12 +11,14 @@ #ifndef hifi_gpu_Stream_h #define hifi_gpu_Stream_h +#include +#include +#include + #include #include "Resource.h" #include "Format.h" -#include -#include namespace gpu { @@ -55,6 +57,8 @@ public: // Every thing that is needed to detail a stream attribute and how to interpret it class Attribute { public: + Attribute() {} + Attribute(Slot slot, Slot channel, Element element, Offset offset = 0, Frequency frequency = PER_VERTEX) : _slot(slot), _channel(channel), @@ -62,21 +66,12 @@ public: _offset(offset), _frequency(frequency) {} - Attribute() : - _slot(POSITION), - _channel(0), - _element(), - _offset(0), - _frequency(PER_VERTEX) - {} - - Slot _slot; // Logical slot assigned to the attribute - Slot _channel; // index of the channel where to get the data from - Element _element; - - Offset _offset; - uint32 _frequency; + Slot _slot{ POSITION }; // Logical slot assigned to the attribute + Slot _channel{ POSITION }; // index of the channel where to get the data from + Element _element{ Element::VEC3F_XYZ }; + Offset _offset{ 0 }; + uint32 _frequency{ PER_VERTEX }; // Size of the uint32 getSize() const { return _element.getSize(); } @@ -113,6 +108,9 @@ public: uint32 getElementTotalSize() const { return _elementTotalSize; } bool setAttribute(Slot slot, Slot channel, Element element, Offset offset = 0, Frequency frequency = PER_VERTEX); + bool setAttribute(Slot slot, Frequency frequency = PER_VERTEX); + bool setAttribute(Slot slot, Slot channel, Frequency frequency = PER_VERTEX); + protected: AttributeMap _attributes; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 8fa3a9a234..a66fda624d 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -348,8 +348,8 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas // need lightmap texcoord UV but doesn't have uv#1 so just reuse the same channel networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, channelNum - 1, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); } - if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW)); + if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW)); } else { int colorsOffset = mesh.tangents.size() * sizeof(glm::vec3); @@ -381,8 +381,8 @@ static NetworkMesh* buildNetworkMesh(const FBXMesh& mesh, const QUrl& textureBas if (mesh.tangents.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TANGENT, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); if (mesh.colors.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::COLOR, channelNum++, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB)); if (mesh.texCoords.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::TEXCOORD, channelNum++, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); - if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::NFLOAT, gpu::XYZW)); + if (mesh.clusterIndices.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, channelNum++, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW)); + if (mesh.clusterWeights.size()) networkMesh->_vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, channelNum++, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW)); } } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 6c7310509a..b120bb8a57 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -187,17 +187,6 @@ gpu::PipelinePointer DeferredLightingEffect::bindSimpleProgram(gpu::Batch& batch return pipeline; } - -void DeferredLightingEffect::renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) { - bindSimpleProgram(batch); - DependencyManager::get()->renderSphere(batch, radius, slices, stacks, color); -} - -void DeferredLightingEffect::renderWireSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color) { - bindSimpleProgram(batch); - DependencyManager::get()->renderSphere(batch, radius, slices, stacks, color, false); -} - uint32_t toCompactColor(const glm::vec4& color) { uint32_t compactColor = ((int(color.x * 255.0f) & 0xFF)) | ((int(color.y * 255.0f) & 0xFF) << 8) | @@ -206,39 +195,103 @@ uint32_t toCompactColor(const glm::vec4& color) { return compactColor; } -void DeferredLightingEffect::renderSolidCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color) { - static const std::string INSTANCE_NAME = __FUNCTION__; - static const size_t TRANSFORM_BUFFER = 0; - static const size_t COLOR_BUFFER = 1; - { - gpu::BufferPointer instanceTransformBuffer = batch.getNamedBuffer(INSTANCE_NAME, TRANSFORM_BUFFER); - glm::mat4 xfmMat4; - instanceTransformBuffer->append(xfm.getMatrix(xfmMat4)); +static const size_t INSTANCE_TRANSFORM_BUFFER = 0; +static const size_t INSTANCE_COLOR_BUFFER = 1; - gpu::BufferPointer instanceColorBuffer = batch.getNamedBuffer(INSTANCE_NAME, COLOR_BUFFER); +template +void renderInstances(const std::string& name, gpu::Batch& batch, const Transform& transform, const glm::vec4& color, F f) { + { + gpu::BufferPointer instanceTransformBuffer = batch.getNamedBuffer(name, INSTANCE_TRANSFORM_BUFFER); + glm::mat4 glmTransform; + instanceTransformBuffer->append(transform.getMatrix(glmTransform)); + + gpu::BufferPointer instanceColorBuffer = batch.getNamedBuffer(name, INSTANCE_COLOR_BUFFER); auto compactColor = toCompactColor(color); instanceColorBuffer->append(compactColor); } - batch.setupNamedCalls(INSTANCE_NAME, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { - auto pipeline = bindSimpleProgram(batch); + auto that = DependencyManager::get(); + batch.setupNamedCalls(name, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + auto pipeline = that->bindSimpleProgram(batch); auto location = pipeline->getProgram()->getUniforms().findLocation("Instanced"); batch._glUniform1i(location, 1); - DependencyManager::get()->renderSolidCubeInstances(batch, data._count, - data._buffers[TRANSFORM_BUFFER], data._buffers[COLOR_BUFFER]); + f(batch, data); batch._glUniform1i(location, 0); }); } -void DeferredLightingEffect::renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color) { - bindSimpleProgram(batch); - DependencyManager::get()->renderSolidCube(batch, size, color); +void DeferredLightingEffect::renderSolidSphereInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) { + static const std::string INSTANCE_NAME = __FUNCTION__; + renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + DependencyManager::get()->renderShapeInstances(batch, GeometryCache::Sphere, data._count, + data._buffers[INSTANCE_TRANSFORM_BUFFER], data._buffers[INSTANCE_COLOR_BUFFER]); + }); } -void DeferredLightingEffect::renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color) { - bindSimpleProgram(batch); - DependencyManager::get()->renderWireCube(batch, size, color); +void DeferredLightingEffect::renderWireSphereInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) { + static const std::string INSTANCE_NAME = __FUNCTION__; + renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + DependencyManager::get()->renderWireShapeInstances(batch, GeometryCache::Sphere, data._count, + data._buffers[INSTANCE_TRANSFORM_BUFFER], data._buffers[INSTANCE_COLOR_BUFFER]); + }); +} + +// Enable this in a debug build to cause 'box' entities to iterate through all the +// available shape types, both solid and wireframes +//#define DEBUG_SHAPES + +void DeferredLightingEffect::renderSolidCubeInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) { + static const std::string INSTANCE_NAME = __FUNCTION__; + +#ifdef DEBUG_SHAPES + static auto startTime = usecTimestampNow(); + renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + + auto usecs = usecTimestampNow(); + usecs -= startTime; + auto msecs = usecs / USECS_PER_MSEC; + float seconds = msecs; + seconds /= MSECS_PER_SECOND; + float fractionalSeconds = seconds - floor(seconds); + int shapeIndex = (int)seconds; + + // Every second we flip to the next shape. + static const int SHAPE_COUNT = 5; + GeometryCache::Shape shapes[SHAPE_COUNT] = { + GeometryCache::Cube, + GeometryCache::Tetrahedron, + GeometryCache::Sphere, + GeometryCache::Icosahedron, + GeometryCache::Line, + }; + + shapeIndex %= SHAPE_COUNT; + GeometryCache::Shape shape = shapes[shapeIndex]; + + // For the first half second for a given shape, show the wireframe, for the second half, show the solid. + if (fractionalSeconds > 0.5f) { + DependencyManager::get()->renderShapeInstances(batch, shape, data._count, + data._buffers[INSTANCE_TRANSFORM_BUFFER], data._buffers[INSTANCE_COLOR_BUFFER]); + } else { + DependencyManager::get()->renderWireShapeInstances(batch, shape, data._count, + data._buffers[INSTANCE_TRANSFORM_BUFFER], data._buffers[INSTANCE_COLOR_BUFFER]); + } + }); +#else + renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + DependencyManager::get()->renderCubeInstances(batch, data._count, + data._buffers[INSTANCE_TRANSFORM_BUFFER], data._buffers[INSTANCE_COLOR_BUFFER]); + }); +#endif +} + +void DeferredLightingEffect::renderWireCubeInstance(gpu::Batch& batch, const Transform& transform, const glm::vec4& color) { + static const std::string INSTANCE_NAME = __FUNCTION__; + renderInstances(INSTANCE_NAME, batch, transform, color, [](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + DependencyManager::get()->renderWireCubeInstances(batch, data._count, + data._buffers[INSTANCE_TRANSFORM_BUFFER], data._buffers[INSTANCE_COLOR_BUFFER]); + }); } void DeferredLightingEffect::renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, const glm::vec3& maxCorner, @@ -546,8 +599,9 @@ void DeferredLightingEffect::render(RenderArgs* args) { } else { Transform model; model.setTranslation(glm::vec3(light->getPosition().x, light->getPosition().y, light->getPosition().z)); - batch.setModelTransform(model); - geometryCache->renderSphere(batch, expandedRadius, 32, 32, glm::vec4(1.0f, 1.0f, 1.0f, 1.0f)); + batch.setModelTransform(model.postScale(expandedRadius)); + batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + geometryCache->renderSphere(batch); } } } diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 83bb4c215f..9c4809a82e 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -40,24 +40,26 @@ public: gpu::PipelinePointer bindSimpleProgram(gpu::Batch& batch, bool textured = false, bool culled = true, bool emmisive = false, bool depthBias = false); - /// Sets up the state necessary to render static untextured geometry with the simple program. - void bindInstanceProgram(gpu::Batch& batch, bool textured = false, bool culled = true, - bool emmisive = false, bool depthBias = false); + void renderSolidSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color); + void renderSolidSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) { + renderSolidSphereInstance(batch, xfm, glm::vec4(color, 1.0)); + } - //// Renders a solid sphere with the simple program. - void renderSolidSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color); + void renderWireSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color); + void renderWireSphereInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) { + renderWireSphereInstance(batch, xfm, glm::vec4(color, 1.0)); + } - //// Renders a wireframe sphere with the simple program. - void renderWireSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color); - - //// Renders a solid cube using instancing. Transform should include scaling. void renderSolidCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color); + void renderSolidCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) { + renderSolidCubeInstance(batch, xfm, glm::vec4(color, 1.0)); + } - //// Renders a solid cube with the simple program. - void renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color); + void renderWireCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec4& color); + void renderWireCubeInstance(gpu::Batch& batch, const Transform& xfm, const glm::vec3& color) { + renderWireCubeInstance(batch, xfm, glm::vec4(color, 1.0)); + } - //// Renders a wireframe cube with the simple program. - void renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color); //// Renders a quad with the simple program. void renderQuad(gpu::Batch& batch, const glm::vec3& minCorner, const glm::vec3& maxCorner, const glm::vec4& color); diff --git a/libraries/render-utils/src/Environment.cpp b/libraries/render-utils/src/Environment.cpp index 605f67f957..7fbd89acc1 100644 --- a/libraries/render-utils/src/Environment.cpp +++ b/libraries/render-utils/src/Environment.cpp @@ -197,7 +197,6 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 } void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, const EnvironmentData& data) { - glm::vec3 center = data.getAtmosphereCenter(); // transform the model transform to the center of our atmosphere @@ -252,5 +251,6 @@ void Environment::renderAtmosphere(gpu::Batch& batch, ViewFrustum& viewFrustum, batch._glUniform1f(locations[G_LOCATION], -0.990f); batch._glUniform1f(locations[G2_LOCATION], -0.990f * -0.990f); - DependencyManager::get()->renderSphere(batch,1.0f, 100, 50, glm::vec4(1.0f, 0.0f, 0.0f, 0.5f)); //Draw a unit sphere + batch._glColor4f(1.0f, 0.0f, 0.0f, 0.5f); + DependencyManager::get()->renderSphere(batch); //Draw a unit sphere } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 89ac761a7a..093434f079 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -35,9 +35,468 @@ const int GeometryCache::UNKNOWN_ID = -1; + +static const uint FLOATS_PER_VERTEX = 3; +static const uint VERTICES_PER_TRIANGLE = 3; +static const uint TRIANGLES_PER_QUAD = 2; +static const uint CUBE_FACES = 6; +static const uint CUBE_VERTICES_PER_FACE = 4; +static const uint CUBE_VERTICES = CUBE_FACES * CUBE_VERTICES_PER_FACE; +static const uint CUBE_VERTEX_POINTS = CUBE_VERTICES * FLOATS_PER_VERTEX; +static const uint CUBE_INDICES = CUBE_FACES * TRIANGLES_PER_QUAD * VERTICES_PER_TRIANGLE; +static const uint SPHERE_LATITUDES = 24; +static const uint SPHERE_MERIDIANS = SPHERE_LATITUDES * 2; +static const uint SPHERE_INDICES = SPHERE_MERIDIANS * (SPHERE_LATITUDES - 1) * TRIANGLES_PER_QUAD * VERTICES_PER_TRIANGLE; + +static const gpu::Element POSITION_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element NORMAL_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; +static const gpu::Element COLOR_ELEMENT{ gpu::VEC4, gpu::NUINT8, gpu::RGBA }; +static const gpu::Element TRANSFORM_ELEMENT{ gpu::MAT4, gpu::FLOAT, gpu::XYZW }; + +static gpu::Stream::FormatPointer SOLID_STREAM_FORMAT; +static gpu::Stream::FormatPointer INSTANCED_SOLID_STREAM_FORMAT; + +static const uint SHAPE_VERTEX_STRIDE = sizeof(glm::vec3) * 2; // vertices and normals +static const uint SHAPE_NORMALS_OFFSET = sizeof(glm::vec3); + + +void GeometryCache::ShapeData::setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices) { + vertexBuffer->append(vertices); + + _positionView = gpu::BufferView(vertexBuffer, 0, + vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, POSITION_ELEMENT); + _normalView = gpu::BufferView(vertexBuffer, SHAPE_NORMALS_OFFSET, + vertexBuffer->getSize(), SHAPE_VERTEX_STRIDE, NORMAL_ELEMENT); +} + +void GeometryCache::ShapeData::setupIndices(gpu::BufferPointer& indexBuffer, const IndexVector& indices, const IndexVector& wireIndices) { + _indices = indexBuffer; + if (!indices.empty()) { + _indexOffset = indexBuffer->getSize(); + _indexCount = indices.size(); + indexBuffer->append(indices); + } + + if (!wireIndices.empty()) { + _wireIndexOffset = indexBuffer->getSize(); + _wireIndexCount = wireIndices.size(); + indexBuffer->append(wireIndices); + } +} + +void GeometryCache::ShapeData::setupBatch(gpu::Batch& batch) const { + batch.setInputBuffer(gpu::Stream::POSITION, _positionView); + batch.setInputBuffer(gpu::Stream::NORMAL, _normalView); +} + +void GeometryCache::ShapeData::draw(gpu::Batch& batch) const { + if (_indexCount) { + setupBatch(batch); + batch.setIndexBuffer(gpu::UINT16, _indices, _indexOffset); + batch.drawIndexed(gpu::TRIANGLES, _indexCount); + } +} + +void GeometryCache::ShapeData::drawWire(gpu::Batch& batch) const { + if (_wireIndexCount) { + setupBatch(batch); + batch.setIndexBuffer(gpu::UINT16, _indices, _wireIndexOffset); + batch.drawIndexed(gpu::LINES, _wireIndexCount); + } +} + +void GeometryCache::ShapeData::drawInstances(gpu::Batch& batch, size_t count) const { + if (_indexCount) { + setupBatch(batch); + batch.setIndexBuffer(gpu::UINT16, _indices, _indexOffset); + batch.drawIndexedInstanced(count, gpu::TRIANGLES, _indexCount); + } +} + +void GeometryCache::ShapeData::drawWireInstances(gpu::Batch& batch, size_t count) const { + if (_wireIndexCount) { + setupBatch(batch); + batch.setIndexBuffer(gpu::UINT16, _indices, _wireIndexOffset); + batch.drawIndexedInstanced(count, gpu::LINES, _wireIndexCount); + } +} + +const VertexVector& icosahedronVertices() { + static const float phi = (1.0 + sqrt(5.0)) / 2.0; + static const float a = 0.5; + static const float b = 1.0 / (2.0 * phi); + + static const VertexVector vertices{ // + vec3(0, b, -a), vec3(-b, a, 0), vec3(b, a, 0), // + vec3(0, b, a), vec3(b, a, 0), vec3(-b, a, 0), // + vec3(0, b, a), vec3(-a, 0, b), vec3(0, -b, a), // + vec3(0, b, a), vec3(0, -b, a), vec3(a, 0, b), // + vec3(0, b, -a), vec3(a, 0, -b), vec3(0, -b, -a),// + vec3(0, b, -a), vec3(0, -b, -a), vec3(-a, 0, -b), // + vec3(0, -b, a), vec3(-b, -a, 0), vec3(b, -a, 0), // + vec3(0, -b, -a), vec3(b, -a, 0), vec3(-b, -a, 0), // + vec3(-b, a, 0), vec3(-a, 0, -b), vec3(-a, 0, b), // + vec3(-b, -a, 0), vec3(-a, 0, b), vec3(-a, 0, -b), // + vec3(b, a, 0), vec3(a, 0, b), vec3(a, 0, -b), // + vec3(b, -a, 0), vec3(a, 0, -b), vec3(a, 0, b), // + vec3(0, b, a), vec3(-b, a, 0), vec3(-a, 0, b), // + vec3(0, b, a), vec3(a, 0, b), vec3(b, a, 0), // + vec3(0, b, -a), vec3(-a, 0, -b), vec3(-b, a, 0), // + vec3(0, b, -a), vec3(b, a, 0), vec3(a, 0, -b), // + vec3(0, -b, -a), vec3(-b, -a, 0), vec3(-a, 0, -b), // + vec3(0, -b, -a), vec3(a, 0, -b), vec3(b, -a, 0), // + vec3(0, -b, a), vec3(-a, 0, b), vec3(-b, -a, 0), // + vec3(0, -b, a), vec3(b, -a, 0), vec3(a, 0, b) + }; // + return vertices; +} + +const VertexVector& tetrahedronVertices() { + static const float a = 1.0f / sqrt(2.0f); + static const auto A = vec3(0, 1, a); + static const auto B = vec3(0, -1, a); + static const auto C = vec3(1, 0, -a); + static const auto D = vec3(-1, 0, -a); + static const VertexVector vertices{ + A, B, C, + D, B, A, + C, D, A, + C, B, D, + }; + return vertices; +} + +static const size_t TESSELTATION_MULTIPLIER = 4; +static const size_t ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT = 3; +static const size_t VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER = 2; + + +VertexVector tesselate(const VertexVector& startingTriangles, int count) { + VertexVector triangles = startingTriangles; + if (0 != (triangles.size() % 3)) { + throw std::runtime_error("Bad number of vertices for tesselation"); + } + + for (size_t i = 0; i < triangles.size(); ++i) { + triangles[i] = glm::normalize(triangles[i]); + } + + VertexVector newTriangles; + while (count) { + newTriangles.clear(); + // Tesselation takes one triangle and makes it into 4 triangles + // See https://en.wikipedia.org/wiki/Space-filling_tree#/media/File:Space_Filling_Tree_Tri_iter_1_2_3.png + newTriangles.reserve(triangles.size() * TESSELTATION_MULTIPLIER); + for (size_t i = 0; i < triangles.size(); i += VERTICES_PER_TRIANGLE) { + const vec3& a = triangles[i]; + const vec3& b = triangles[i + 1]; + const vec3& c = triangles[i + 2]; + vec3 ab = glm::normalize(a + b); + vec3 bc = glm::normalize(b + c); + vec3 ca = glm::normalize(c + a); + + newTriangles.push_back(a); + newTriangles.push_back(ab); + newTriangles.push_back(ca); + + newTriangles.push_back(b); + newTriangles.push_back(bc); + newTriangles.push_back(ab); + + newTriangles.push_back(c); + newTriangles.push_back(ca); + newTriangles.push_back(bc); + + newTriangles.push_back(ab); + newTriangles.push_back(bc); + newTriangles.push_back(ca); + } + triangles.swap(newTriangles); + --count; + } + return triangles; +} + +// FIXME solids need per-face vertices, but smooth shaded +// components do not. Find a way to support using draw elements +// or draw arrays as appropriate +// Maybe special case cone and cylinder since they combine flat +// and smooth shading +void GeometryCache::buildShapes() { + auto vertexBuffer = std::make_shared(); + auto indexBuffer = std::make_shared(); + uint16_t startingIndex = 0; + + // Cube + startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; + { + ShapeData& shapeData = _shapes[Cube]; + VertexVector vertices; + // front + vertices.push_back(vec3(1, 1, 1)); + vertices.push_back(vec3(0, 0, 1)); + vertices.push_back(vec3(-1, 1, 1)); + vertices.push_back(vec3(0, 0, 1)); + vertices.push_back(vec3(-1, -1, 1)); + vertices.push_back(vec3(0, 0, 1)); + vertices.push_back(vec3(1, -1, 1)); + vertices.push_back(vec3(0, 0, 1)); + + // right + vertices.push_back(vec3(1, 1, 1)); + vertices.push_back(vec3(1, 0, 0)); + vertices.push_back(vec3(1, -1, 1)); + vertices.push_back(vec3(1, 0, 0)); + vertices.push_back(vec3(1, -1, -1)); + vertices.push_back(vec3(1, 0, 0)); + vertices.push_back(vec3(1, 1, -1)); + vertices.push_back(vec3(1, 0, 0)); + + // top + vertices.push_back(vec3(1, 1, 1)); + vertices.push_back(vec3(0, 1, 0)); + vertices.push_back(vec3(1, 1, -1)); + vertices.push_back(vec3(0, 1, 0)); + vertices.push_back(vec3(-1, 1, -1)); + vertices.push_back(vec3(0, 1, 0)); + vertices.push_back(vec3(-1, 1, 1)); + vertices.push_back(vec3(0, 1, 0)); + + // left + vertices.push_back(vec3(-1, 1, 1)); + vertices.push_back(vec3(-1, 0, 0)); + vertices.push_back(vec3(-1, 1, -1)); + vertices.push_back(vec3(-1, 0, 0)); + vertices.push_back(vec3(-1, -1, -1)); + vertices.push_back(vec3(-1, 0, 0)); + vertices.push_back(vec3(-1, -1, 1)); + vertices.push_back(vec3(-1, 0, 0)); + + // bottom + vertices.push_back(vec3(-1, -1, -1)); + vertices.push_back(vec3(0, -1, 0)); + vertices.push_back(vec3(1, -1, -1)); + vertices.push_back(vec3(0, -1, 0)); + vertices.push_back(vec3(1, -1, 1)); + vertices.push_back(vec3(0, -1, 0)); + vertices.push_back(vec3(-1, -1, 1)); + vertices.push_back(vec3(0, -1, 0)); + + // back + vertices.push_back(vec3(1, -1, -1)); + vertices.push_back(vec3(0, 0, -1)); + vertices.push_back(vec3(-1, -1, -1)); + vertices.push_back(vec3(0, 0, -1)); + vertices.push_back(vec3(-1, 1, -1)); + vertices.push_back(vec3(0, 0, -1)); + vertices.push_back(vec3(1, 1, -1)); + vertices.push_back(vec3(0, 0, -1)); + + static const size_t VERTEX_FORMAT_SIZE = 2; + static const size_t VERTEX_OFFSET = 0; + static const size_t NORMAL_OFFSET = 1; + + for (size_t i = 0; i < vertices.size(); ++i) { + auto vertexIndex = i; + // Make a unit cube by having the vertices (at index N) + // while leaving the normals (at index N + 1) alone + if (VERTEX_OFFSET == vertexIndex % VERTEX_FORMAT_SIZE) { + vertices[vertexIndex] *= 0.5f; + } + } + shapeData.setupVertices(_shapeVertices, vertices); + + IndexVector indices{ + 0, 1, 2, 2, 3, 0, // front + 4, 5, 6, 6, 7, 4, // right + 8, 9, 10, 10, 11, 8, // top + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // bottom + 20, 21, 22, 22, 23, 20 // back + }; + for (auto& index : indices) { + index += startingIndex; + } + + IndexVector wireIndices{ + 0, 1, 1, 2, 2, 3, 3, 0, // front + 20, 21, 21, 22, 22, 23, 23, 20, // back + 0, 23, 1, 22, 2, 21, 3, 20 // sides + }; + for (int i = 0; i < wireIndices.size(); ++i) { + indices[i] += startingIndex; + } + + shapeData.setupIndices(_shapeIndices, indices, wireIndices); + } + + // Tetrahedron + startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; + { + ShapeData& shapeData = _shapes[Tetrahedron]; + size_t vertexCount = 4; + VertexVector vertices; + { + VertexVector originalVertices = tetrahedronVertices(); + vertexCount = originalVertices.size(); + vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); + for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) { + auto triangleStartIndex = i; + vec3 faceNormal; + for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { + auto triangleVertexIndex = j; + auto vertexIndex = triangleStartIndex + triangleVertexIndex; + faceNormal += originalVertices[vertexIndex]; + } + faceNormal = glm::normalize(faceNormal); + for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { + auto triangleVertexIndex = j; + auto vertexIndex = triangleStartIndex + triangleVertexIndex; + vertices.push_back(glm::normalize(originalVertices[vertexIndex])); + vertices.push_back(faceNormal); + } + } + } + shapeData.setupVertices(_shapeVertices, vertices); + + IndexVector indices; + for (size_t i = 0; i < vertexCount; i += VERTICES_PER_TRIANGLE) { + auto triangleStartIndex = i; + for (size_t j = 0; j < VERTICES_PER_TRIANGLE; ++j) { + auto triangleVertexIndex = j; + auto vertexIndex = triangleStartIndex + triangleVertexIndex; + indices.push_back(vertexIndex + startingIndex); + } + } + + IndexVector wireIndices{ + 0, 1, 1, 2, 2, 0, + 0, 3, 1, 3, 2, 3, + }; + + for (int i = 0; i < wireIndices.size(); ++i) { + wireIndices[i] += startingIndex; + } + + shapeData.setupIndices(_shapeIndices, indices, wireIndices); + } + + // Sphere + // FIXME this uses way more vertices than required. Should find a way to calculate the indices + // using shared vertices for better vertex caching + startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; + { + ShapeData& shapeData = _shapes[Sphere]; + VertexVector vertices; + IndexVector indices; + { + VertexVector originalVertices = tesselate(icosahedronVertices(), ICOSAHEDRON_TO_SPHERE_TESSELATION_COUNT); + vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); + for (size_t i = 0; i < originalVertices.size(); i += VERTICES_PER_TRIANGLE) { + auto triangleStartIndex = i; + for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { + auto triangleVertexIndex = j; + auto vertexIndex = triangleStartIndex + triangleVertexIndex; + const auto& vertex = originalVertices[i + j]; + // Spheres use the same values for vertices and normals + vertices.push_back(vertex); + vertices.push_back(vertex); + indices.push_back(vertexIndex + startingIndex); + } + } + } + + shapeData.setupVertices(_shapeVertices, vertices); + // FIXME don't use solid indices for wire drawing. + shapeData.setupIndices(_shapeIndices, indices, indices); + } + + // Icosahedron + startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; + { + ShapeData& shapeData = _shapes[Icosahedron]; + + VertexVector vertices; + IndexVector indices; + { + const VertexVector& originalVertices = icosahedronVertices(); + vertices.reserve(originalVertices.size() * VECTOR_TO_VECTOR_WITH_NORMAL_MULTIPLER); + for (size_t i = 0; i < originalVertices.size(); i += 3) { + auto triangleStartIndex = i; + vec3 faceNormal; + for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { + auto triangleVertexIndex = j; + auto vertexIndex = triangleStartIndex + triangleVertexIndex; + faceNormal += originalVertices[vertexIndex]; + } + faceNormal = glm::normalize(faceNormal); + for (int j = 0; j < VERTICES_PER_TRIANGLE; ++j) { + auto triangleVertexIndex = j; + auto vertexIndex = triangleStartIndex + triangleVertexIndex; + vertices.push_back(glm::normalize(originalVertices[vertexIndex])); + vertices.push_back(faceNormal); + indices.push_back(vertexIndex + startingIndex); + } + } + } + + shapeData.setupVertices(_shapeVertices, vertices); + // FIXME don't use solid indices for wire drawing. + shapeData.setupIndices(_shapeIndices, indices, indices); + } + + // Line + startingIndex = _shapeVertices->getSize() / SHAPE_VERTEX_STRIDE; + { + ShapeData& shapeData = _shapes[Line]; + shapeData.setupVertices(_shapeVertices, VertexVector{ + vec3(-0.5, 0, 0), vec3(-0.5f, 0, 0), + vec3(0.5f, 0, 0), vec3(0.5f, 0, 0) + }); + IndexVector wireIndices; + // Only two indices + wireIndices.push_back(0 + startingIndex); + wireIndices.push_back(1 + startingIndex); + + shapeData.setupIndices(_shapeIndices, IndexVector(), wireIndices); + } + + // Not implememented yet: + + //Triangle, + //Quad, + //Circle, + //Octahetron, + //Dodecahedron, + //Torus, + //Cone, + //Cylinder, +} + +gpu::Stream::FormatPointer& getSolidStreamFormat() { + if (!SOLID_STREAM_FORMAT) { + SOLID_STREAM_FORMAT = std::make_shared(); // 1 for everyone + SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT); + SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT); + } + return SOLID_STREAM_FORMAT; +} + +gpu::Stream::FormatPointer& getInstancedSolidStreamFormat() { + if (!INSTANCED_SOLID_STREAM_FORMAT) { + INSTANCED_SOLID_STREAM_FORMAT = std::make_shared(); // 1 for everyone + INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, POSITION_ELEMENT); + INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, NORMAL_ELEMENT); + INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE); + INSTANCED_SOLID_STREAM_FORMAT->setAttribute(gpu::Stream::INSTANCE_XFM, gpu::Stream::INSTANCE_XFM, TRANSFORM_ELEMENT, 0, gpu::Stream::PER_INSTANCE); + } + return INSTANCED_SOLID_STREAM_FORMAT; +} + GeometryCache::GeometryCache() : _nextID(0) { + buildShapes(); } GeometryCache::~GeometryCache() { @@ -49,255 +508,64 @@ GeometryCache::~GeometryCache() { #endif //def WANT_DEBUG } -const int NUM_VERTICES_PER_TRIANGLE = 3; -const int NUM_TRIANGLES_PER_QUAD = 2; -const int NUM_VERTICES_PER_TRIANGULATED_QUAD = NUM_VERTICES_PER_TRIANGLE * NUM_TRIANGLES_PER_QUAD; -const int NUM_COORDS_PER_VERTEX = 3; - -void GeometryCache::renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color, bool solid, int id) { - bool registered = (id != UNKNOWN_ID); - - Vec2Pair radiusKey(glm::vec2(radius, slices), glm::vec2(stacks, 0)); - IntPair slicesStacksKey(slices, stacks); - Vec3Pair colorKey(glm::vec3(color.x, color.y, slices), glm::vec3(color.z, color.w, stacks)); - - int vertices = slices * (stacks - 1) + 2; - int indices = slices * (stacks - 1) * NUM_VERTICES_PER_TRIANGULATED_QUAD; - - if ((registered && (!_registeredSphereVertices.contains(id) || _lastRegisteredSphereVertices[id] != radiusKey)) - || (!registered && !_sphereVertices.contains(radiusKey))) { - - if (registered && _registeredSphereVertices.contains(id)) { - _registeredSphereVertices[id].reset(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderSphere()... RELEASING REGISTERED VERTICES BUFFER"; - #endif - } - - auto verticesBuffer = std::make_shared(); - if (registered) { - _registeredSphereVertices[id] = verticesBuffer; - _lastRegisteredSphereVertices[id] = radiusKey; - } else { - _sphereVertices[radiusKey] = verticesBuffer; - } - - GLfloat* vertexData = new GLfloat[vertices * NUM_COORDS_PER_VERTEX]; - GLfloat* vertex = vertexData; - - // south pole - *(vertex++) = 0.0f; - *(vertex++) = 0.0f; - *(vertex++) = -1.0f * radius; - - //add stacks vertices climbing up Y axis - for (int i = 1; i < stacks; i++) { - float phi = PI * (float)i / (float)(stacks) - PI_OVER_TWO; - float z = sinf(phi) * radius; - float stackRadius = cosf(phi) * radius; - - for (int j = 0; j < slices; j++) { - float theta = TWO_PI * (float)j / (float)slices; - - *(vertex++) = sinf(theta) * stackRadius; - *(vertex++) = cosf(theta) * stackRadius; - *(vertex++) = z; - } - } - - // north pole - *(vertex++) = 0.0f; - *(vertex++) = 0.0f; - *(vertex++) = 1.0f * radius; - - verticesBuffer->append(sizeof(GLfloat) * vertices * NUM_COORDS_PER_VERTEX, (gpu::Byte*) vertexData); - delete[] vertexData; - - #ifdef WANT_DEBUG - qCDebug(renderutils) << "GeometryCache::renderSphere()... --- CREATING VERTICES BUFFER"; - qCDebug(renderutils) << " radius:" << radius; - qCDebug(renderutils) << " slices:" << slices; - qCDebug(renderutils) << " stacks:" << stacks; - - qCDebug(renderutils) << " _sphereVertices.size():" << _sphereVertices.size(); - #endif - } - #ifdef WANT_DEBUG - else if (registered) { - qCDebug(renderutils) << "renderSphere()... REUSING PREVIOUSLY REGISTERED VERTICES BUFFER"; - } - #endif - - if ((registered && (!_registeredSphereIndices.contains(id) || _lastRegisteredSphereIndices[id] != slicesStacksKey)) - || (!registered && !_sphereIndices.contains(slicesStacksKey))) { - - if (registered && _registeredSphereIndices.contains(id)) { - _registeredSphereIndices[id].reset(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderSphere()... RELEASING REGISTERED INDICES BUFFER"; - #endif - } - - auto indicesBuffer = std::make_shared(); - if (registered) { - _registeredSphereIndices[id] = indicesBuffer; - _lastRegisteredSphereIndices[id] = slicesStacksKey; - } else { - _sphereIndices[slicesStacksKey] = indicesBuffer; - } - - GLushort* indexData = new GLushort[indices]; - GLushort* index = indexData; - - int indexCount = 0; - - // South cap - GLushort bottom = 0; - GLushort top = 1; - for (int i = 0; i < slices; i++) { - *(index++) = bottom; - *(index++) = top + i; - *(index++) = top + (i + 1) % slices; - - indexCount += 3; - } - - // (stacks - 2) ribbons - for (int i = 0; i < stacks - 2; i++) { - bottom = i * slices + 1; - top = bottom + slices; - for (int j = 0; j < slices; j++) { - int next = (j + 1) % slices; - - *(index++) = top + next; - *(index++) = bottom + j; - *(index++) = top + j; - - indexCount += 3; - - *(index++) = bottom + next; - *(index++) = bottom + j; - *(index++) = top + next; - - indexCount += 3; - - } - } - - // north cap - bottom = (stacks - 2) * slices + 1; - top = bottom + slices; - for (int i = 0; i < slices; i++) { - *(index++) = bottom + (i + 1) % slices; - *(index++) = bottom + i; - *(index++) = top; - - indexCount += 3; - - } - indicesBuffer->append(sizeof(GLushort) * indices, (gpu::Byte*) indexData); - delete[] indexData; - - #ifdef WANT_DEBUG - qCDebug(renderutils) << "GeometryCache::renderSphere()... --- CREATING INDICES BUFFER"; - qCDebug(renderutils) << " radius:" << radius; - qCDebug(renderutils) << " slices:" << slices; - qCDebug(renderutils) << " stacks:" << stacks; - qCDebug(renderutils) << "indexCount:" << indexCount; - qCDebug(renderutils) << " indices:" << indices; - qCDebug(renderutils) << " _sphereIndices.size():" << _sphereIndices.size(); - #endif - } - #ifdef WANT_DEBUG - else if (registered) { - qCDebug(renderutils) << "renderSphere()... REUSING PREVIOUSLY REGISTERED INDICES BUFFER"; - } - #endif - - if ((registered && (!_registeredSphereColors.contains(id) || _lastRegisteredSphereColors[id] != colorKey)) - || (!registered && !_sphereColors.contains(colorKey))) { - - if (registered && _registeredSphereColors.contains(id)) { - _registeredSphereColors[id].reset(); - #ifdef WANT_DEBUG - qCDebug(renderutils) << "renderSphere()... RELEASING REGISTERED COLORS BUFFER"; - #endif - } - - auto colorBuffer = std::make_shared(); - if (registered) { - _registeredSphereColors[id] = colorBuffer; - _lastRegisteredSphereColors[id] = colorKey; - } else { - _sphereColors[colorKey] = colorBuffer; - } - - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - - int* colorData = new int[vertices]; - int* colorDataAt = colorData; - - for(int v = 0; v < vertices; v++) { - *(colorDataAt++) = compactColor; - } - - colorBuffer->append(sizeof(int) * vertices, (gpu::Byte*) colorData); - delete[] colorData; - - #ifdef WANT_DEBUG - qCDebug(renderutils) << "GeometryCache::renderSphere()... --- CREATING COLORS BUFFER"; - qCDebug(renderutils) << " vertices:" << vertices; - qCDebug(renderutils) << " color:" << color; - qCDebug(renderutils) << " slices:" << slices; - qCDebug(renderutils) << " stacks:" << stacks; - qCDebug(renderutils) << " _sphereColors.size():" << _sphereColors.size(); - #endif - } - #ifdef WANT_DEBUG - else if (registered) { - qCDebug(renderutils) << "renderSphere()... REUSING PREVIOUSLY REGISTERED COLORS BUFFER"; - } - #endif - - gpu::BufferPointer verticesBuffer = registered ? _registeredSphereVertices[id] : _sphereVertices[radiusKey]; - gpu::BufferPointer indicesBuffer = registered ? _registeredSphereIndices[id] : _sphereIndices[slicesStacksKey]; - gpu::BufferPointer colorBuffer = registered ? _registeredSphereColors[id] : _sphereColors[colorKey]; - - const int VERTICES_SLOT = 0; - const int NORMALS_SLOT = 1; - const int COLOR_SLOT = 2; - static gpu::Stream::FormatPointer streamFormat; - static gpu::Element positionElement, normalElement, colorElement; - if (!streamFormat) { - streamFormat = std::make_shared(); // 1 for everyone - streamFormat->setAttribute(gpu::Stream::POSITION, VERTICES_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - streamFormat->setAttribute(gpu::Stream::NORMAL, NORMALS_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); - positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element; - normalElement = streamFormat->getAttributes().at(gpu::Stream::NORMAL)._element; - colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element; - } - - gpu::BufferView verticesView(verticesBuffer, positionElement); - gpu::BufferView normalsView(verticesBuffer, normalElement); - gpu::BufferView colorView(colorBuffer, colorElement); - - batch.setInputFormat(streamFormat); - batch.setInputBuffer(VERTICES_SLOT, verticesView); - batch.setInputBuffer(NORMALS_SLOT, normalsView); - batch.setInputBuffer(COLOR_SLOT, colorView); - batch.setIndexBuffer(gpu::UINT16, indicesBuffer, 0); - - if (solid) { - batch.drawIndexed(gpu::TRIANGLES, indices); - } else { - batch.drawIndexed(gpu::LINES, indices); - } +void setupBatchInstance(gpu::Batch& batch, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) { + gpu::BufferView colorView(colorBuffer, COLOR_ELEMENT); + batch.setInputBuffer(gpu::Stream::COLOR, colorView); + gpu::BufferView instanceXfmView(transformBuffer, 0, transformBuffer->getSize(), TRANSFORM_ELEMENT); + batch.setInputBuffer(gpu::Stream::INSTANCE_XFM, instanceXfmView); } +void GeometryCache::renderShape(gpu::Batch& batch, Shape shape) { + batch.setInputFormat(getSolidStreamFormat()); + _shapes[shape].draw(batch); +} + +void GeometryCache::renderWireShape(gpu::Batch& batch, Shape shape) { + batch.setInputFormat(getSolidStreamFormat()); + _shapes[shape].drawWire(batch); +} + +void GeometryCache::renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer) { + batch.setInputFormat(getInstancedSolidStreamFormat()); + setupBatchInstance(batch, transformBuffer, colorBuffer); + _shapes[shape].drawInstances(batch, count); +} + +void GeometryCache::renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer) { + batch.setInputFormat(getInstancedSolidStreamFormat()); + setupBatchInstance(batch, transformBuffer, colorBuffer); + _shapes[shape].drawWireInstances(batch, count); +} + +void GeometryCache::renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) { + renderShapeInstances(batch, Cube, count, transformBuffer, colorBuffer); +} + +void GeometryCache::renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) { + renderWireShapeInstances(batch, Cube, count, transformBuffer, colorBuffer); +} + +void GeometryCache::renderCube(gpu::Batch& batch) { + renderShape(batch, Cube); +} + +void GeometryCache::renderWireCube(gpu::Batch& batch) { + renderWireShape(batch, Cube); +} + +void GeometryCache::renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) { + renderShapeInstances(batch, Sphere, count, transformBuffer, colorBuffer); +} + +void GeometryCache::renderSphere(gpu::Batch& batch) { + renderShape(batch, Sphere); +} + +void GeometryCache::renderWireSphere(gpu::Batch& batch) { + renderWireShape(batch, Sphere); +} + + void GeometryCache::renderGrid(gpu::Batch& batch, int xDivisions, int yDivisions, const glm::vec4& color) { IntPair key(xDivisions, yDivisions); Vec3Pair colorKey(glm::vec3(color.x, color.y, yDivisions), glm::vec3(color.z, color.y, xDivisions)); @@ -682,243 +950,6 @@ void GeometryCache::renderVertices(gpu::Batch& batch, gpu::Primitive primitiveTy } } -static const int FLOATS_PER_VERTEX = 3; -static const int VERTICES_PER_TRIANGLE = 3; - -static const int CUBE_NUMBER_OF_FACES = 6; -static const int CUBE_VERTICES_PER_FACE = 4; -static const int CUBE_TRIANGLES_PER_FACE = 2; -static const int CUBE_VERTICES = CUBE_NUMBER_OF_FACES * CUBE_VERTICES_PER_FACE; -static const int CUBE_VERTEX_POINTS = CUBE_VERTICES * FLOATS_PER_VERTEX; -static const int CUBE_INDICES = CUBE_NUMBER_OF_FACES * CUBE_TRIANGLES_PER_FACE * VERTICES_PER_TRIANGLE; - -static const gpu::Element CUBE_POSITION_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; -static const gpu::Element CUBE_NORMAL_ELEMENT{ gpu::VEC3, gpu::FLOAT, gpu::XYZ }; -static const gpu::Element CUBE_COLOR_ELEMENT{ gpu::VEC4, gpu::NUINT8, gpu::RGBA }; -static const gpu::Element INSTANCE_XFM_ELEMENT{ gpu::MAT4, gpu::FLOAT, gpu::XYZW }; - -gpu::BufferPointer GeometryCache::getCubeVertices(float size) { - if (!_solidCubeVertices.contains(size)) { - auto verticesBuffer = std::make_shared(); - _solidCubeVertices[size] = verticesBuffer; - - GLfloat* vertexData = new GLfloat[CUBE_VERTEX_POINTS * 2]; // vertices and normals - GLfloat* vertex = vertexData; - float halfSize = size / 2.0f; - - static GLfloat cannonicalVertices[CUBE_VERTEX_POINTS] = - { 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0,v1,v2,v3 (front) - 1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0,v3,v4,v5 (right) - 1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0,v5,v6,v1 (top) - -1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1, // v1,v6,v7,v2 (left) - -1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1, // v7,v4,v3,v2 (bottom) - 1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1 }; // v4,v7,v6,v5 (back) - - // normal array - static GLfloat cannonicalNormals[CUBE_VERTEX_POINTS] = - { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0,v1,v2,v3 (front) - 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0,v3,v4,v5 (right) - 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0,v5,v6,v1 (top) - -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1,v6,v7,v2 (left) - 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7,v4,v3,v2 (bottom) - 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1 }; // v4,v7,v6,v5 (back) - - - GLfloat* cannonicalVertex = &cannonicalVertices[0]; - GLfloat* cannonicalNormal = &cannonicalNormals[0]; - - for (int i = 0; i < CUBE_VERTICES; i++) { - // vertices - *(vertex++) = halfSize * *cannonicalVertex++; - *(vertex++) = halfSize * *cannonicalVertex++; - *(vertex++) = halfSize * *cannonicalVertex++; - - //normals - *(vertex++) = *cannonicalNormal++; - *(vertex++) = *cannonicalNormal++; - *(vertex++) = *cannonicalNormal++; - } - verticesBuffer->append(sizeof(GLfloat) * CUBE_VERTEX_POINTS * 2, (gpu::Byte*) vertexData); - } - - return _solidCubeVertices[size]; -} - -gpu::BufferPointer GeometryCache::getSolidCubeIndices() { - if (!_solidCubeIndexBuffer) { - static GLubyte cannonicalIndices[CUBE_INDICES] = { 0, 1, 2, 2, 3, 0, // front - 4, 5, 6, 6, 7, 4, // right - 8, 9,10, 10,11, 8, // top - 12,13,14, 14,15,12, // left - 16,17,18, 18,19,16, // bottom - 20,21,22, 22,23,20 }; // back - - auto indexBuffer = std::make_shared(); - _solidCubeIndexBuffer = indexBuffer; - - _solidCubeIndexBuffer->append(sizeof(cannonicalIndices), (gpu::Byte*) cannonicalIndices); - } - return _solidCubeIndexBuffer; -} - - -void GeometryCache::setupCubeVertices(gpu::Batch& batch, gpu::BufferPointer& verticesBuffer) { - static const int VERTEX_STRIDE = sizeof(GLfloat) * FLOATS_PER_VERTEX * 2; // vertices and normals - static const int NORMALS_OFFSET = sizeof(GLfloat) * FLOATS_PER_VERTEX; - - gpu::BufferView verticesView(verticesBuffer, 0, verticesBuffer->getSize(), VERTEX_STRIDE, CUBE_POSITION_ELEMENT); - gpu::BufferView normalsView(verticesBuffer, NORMALS_OFFSET, verticesBuffer->getSize(), VERTEX_STRIDE, CUBE_NORMAL_ELEMENT); - batch.setInputBuffer(gpu::Stream::POSITION, verticesView); - batch.setInputBuffer(gpu::Stream::NORMAL, normalsView); -} - -void GeometryCache::renderSolidCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer) { - static gpu::Stream::FormatPointer streamFormat; - if (!streamFormat) { - streamFormat = std::make_shared(); // 1 for everyone - streamFormat->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, CUBE_POSITION_ELEMENT, 0); - streamFormat->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, CUBE_NORMAL_ELEMENT); - streamFormat->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, CUBE_COLOR_ELEMENT, 0, gpu::Stream::PER_INSTANCE); - streamFormat->setAttribute(gpu::Stream::INSTANCE_XFM, gpu::Stream::INSTANCE_XFM, INSTANCE_XFM_ELEMENT, 0, gpu::Stream::PER_INSTANCE); - } - batch.setInputFormat(streamFormat); - - gpu::BufferView colorView(colorBuffer, CUBE_COLOR_ELEMENT); - batch.setInputBuffer(gpu::Stream::COLOR, colorView); - - gpu::BufferView instanceXfmView(transformBuffer, 0, transformBuffer->getSize(), INSTANCE_XFM_ELEMENT); - batch.setInputBuffer(gpu::Stream::INSTANCE_XFM, instanceXfmView); - - gpu::BufferPointer verticesBuffer = getCubeVertices(1.0); - setupCubeVertices(batch, verticesBuffer); - batch.setIndexBuffer(gpu::UINT8, getSolidCubeIndices(), 0); - batch.drawIndexedInstanced(count, gpu::TRIANGLES, CUBE_INDICES); -} - - -void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color) { - Vec2Pair colorKey(glm::vec2(color.x, color.y), glm::vec2(color.z, color.y)); - if (!_solidCubeColors.contains(colorKey)) { - auto colorBuffer = std::make_shared(); - _solidCubeColors[colorKey] = colorBuffer; - - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - int colors[CUBE_VERTICES] = { - compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor - }; - colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); - } - gpu::BufferPointer colorBuffer = _solidCubeColors[colorKey]; - - static gpu::Stream::FormatPointer streamFormat; - if (!streamFormat) { - streamFormat = std::make_shared(); // 1 for everyone - streamFormat->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, CUBE_POSITION_ELEMENT, 0); - streamFormat->setAttribute(gpu::Stream::NORMAL, gpu::Stream::NORMAL, CUBE_NORMAL_ELEMENT); - streamFormat->setAttribute(gpu::Stream::COLOR, gpu::Stream::COLOR, CUBE_COLOR_ELEMENT); - } - batch.setInputFormat(streamFormat); - - gpu::BufferView colorView(colorBuffer, CUBE_COLOR_ELEMENT); - batch.setInputBuffer(gpu::Stream::COLOR, colorView); - - gpu::BufferPointer verticesBuffer = getCubeVertices(size); - setupCubeVertices(batch, verticesBuffer); - - batch.setIndexBuffer(gpu::UINT8, getSolidCubeIndices(), 0); - batch.drawIndexed(gpu::TRIANGLES, CUBE_INDICES); -} - - -void GeometryCache::renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color) { - Vec2Pair colorKey(glm::vec2(color.x, color.y),glm::vec2(color.z, color.y)); - static const int WIRE_CUBE_VERTICES_PER_EDGE = 2; - static const int WIRE_CUBE_TOP_EDGES = 4; - static const int WIRE_CUBE_BOTTOM_EDGES = 4; - static const int WIRE_CUBE_SIDE_EDGES = 4; - static const int WIRE_CUBE_VERTICES = 8; - static const int WIRE_CUBE_INDICES = (WIRE_CUBE_TOP_EDGES + WIRE_CUBE_BOTTOM_EDGES + WIRE_CUBE_SIDE_EDGES) * WIRE_CUBE_VERTICES_PER_EDGE; - - if (!_cubeVerticies.contains(size)) { - auto verticesBuffer = std::make_shared(); - _cubeVerticies[size] = verticesBuffer; - - static const int WIRE_CUBE_VERTEX_POINTS = WIRE_CUBE_VERTICES * FLOATS_PER_VERTEX; - GLfloat* vertexData = new GLfloat[WIRE_CUBE_VERTEX_POINTS]; // only vertices, no normals because we're a wire cube - GLfloat* vertex = vertexData; - float halfSize = size / 2.0f; - - static GLfloat cannonicalVertices[] = - { 1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0, v1, v2, v3 (top) - 1,-1, 1, 1,-1,-1, -1,-1,-1, -1,-1, 1 // v4, v5, v6, v7 (bottom) - }; - - for (int i = 0; i < WIRE_CUBE_VERTEX_POINTS; i++) { - vertex[i] = cannonicalVertices[i] * halfSize; - } - - verticesBuffer->append(sizeof(GLfloat) * WIRE_CUBE_VERTEX_POINTS, (gpu::Byte*) vertexData); // I'm skeptical that this is right - } - - if (!_wireCubeIndexBuffer) { - static GLubyte cannonicalIndices[WIRE_CUBE_INDICES] = { - 0, 1, 1, 2, 2, 3, 3, 0, // (top) - 4, 5, 5, 6, 6, 7, 7, 4, // (bottom) - 0, 4, 1, 5, 2, 6, 3, 7, // (side edges) - }; - - auto indexBuffer = std::make_shared(); - _wireCubeIndexBuffer = indexBuffer; - - _wireCubeIndexBuffer->append(sizeof(cannonicalIndices), (gpu::Byte*) cannonicalIndices); - } - - if (!_cubeColors.contains(colorKey)) { - auto colorBuffer = std::make_shared(); - _cubeColors[colorKey] = colorBuffer; - - const int NUM_COLOR_SCALARS_PER_CUBE = 8; - int compactColor = ((int(color.x * 255.0f) & 0xFF)) | - ((int(color.y * 255.0f) & 0xFF) << 8) | - ((int(color.z * 255.0f) & 0xFF) << 16) | - ((int(color.w * 255.0f) & 0xFF) << 24); - int colors[NUM_COLOR_SCALARS_PER_CUBE] = { compactColor, compactColor, compactColor, compactColor, - compactColor, compactColor, compactColor, compactColor }; - - colorBuffer->append(sizeof(colors), (gpu::Byte*) colors); - } - gpu::BufferPointer verticesBuffer = _cubeVerticies[size]; - gpu::BufferPointer colorBuffer = _cubeColors[colorKey]; - - const int VERTICES_SLOT = 0; - const int COLOR_SLOT = 1; - static gpu::Stream::FormatPointer streamFormat; - static gpu::Element positionElement, colorElement; - if (!streamFormat) { - streamFormat = std::make_shared(); // 1 for everyone - streamFormat->setAttribute(gpu::Stream::POSITION, VERTICES_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_SLOT, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA)); - positionElement = streamFormat->getAttributes().at(gpu::Stream::POSITION)._element; - colorElement = streamFormat->getAttributes().at(gpu::Stream::COLOR)._element; - } - - gpu::BufferView verticesView(verticesBuffer, positionElement); - gpu::BufferView colorView(colorBuffer, colorElement); - - batch.setInputFormat(streamFormat); - batch.setInputBuffer(VERTICES_SLOT, verticesView); - batch.setInputBuffer(COLOR_SLOT, colorView); - batch.setIndexBuffer(gpu::UINT8, _wireCubeIndexBuffer, 0); - batch.drawIndexed(gpu::LINES, WIRE_CUBE_INDICES); -} void GeometryCache::renderBevelCornersRect(gpu::Batch& batch, int x, int y, int width, int height, int bevelDistance, const glm::vec4& color, int id) { bool registered = (id != UNKNOWN_ID); @@ -1092,11 +1123,6 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co batch.draw(gpu::TRIANGLE_STRIP, 4, 0); } -void GeometryCache::renderUnitCube(gpu::Batch& batch) { - static const glm::vec4 color(1); - renderSolidCube(batch, 1, color); -} - void GeometryCache::renderUnitQuad(gpu::Batch& batch, const glm::vec4& color, int id) { static const glm::vec2 topLeft(-1, 1); static const glm::vec2 bottomRight(1, -1); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 6102489b5c..2e0d0a5493 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -14,6 +14,9 @@ #include "model-networking/ModelCache.h" +#include + + #include #include @@ -115,33 +118,55 @@ inline uint qHash(const Vec4PairVec4Pair& v, uint seed) { seed); } +using VertexVector = std::vector; +using IndexVector = std::vector; + /// Stores cached geometry. class GeometryCache : public Dependency { SINGLETON_DEPENDENCY public: + enum Shape { + Line, + Triangle, + Quad, + Circle, + Cube, + Sphere, + Tetrahedron, + Octahetron, + Dodecahedron, + Icosahedron, + Torus, + Cone, + Cylinder, + + NUM_SHAPES, + }; + int allocateID() { return _nextID++; } static const int UNKNOWN_ID; - gpu::BufferPointer getCubeVertices(float size); - void setupCubeVertices(gpu::Batch& batch, gpu::BufferPointer& verticesBuffer); + void renderShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer); + void renderWireShapeInstances(gpu::Batch& batch, Shape shape, size_t count, gpu::BufferPointer& transformBuffer, gpu::BufferPointer& colorBuffer); + void renderShape(gpu::Batch& batch, Shape shape); + void renderWireShape(gpu::Batch& batch, Shape shape); - gpu::BufferPointer getSolidCubeIndices(); + void renderCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); + void renderWireCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); + void renderCube(gpu::Batch& batch); + void renderWireCube(gpu::Batch& batch); - void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec3& color, bool solid = true, int id = UNKNOWN_ID) - { renderSphere(batch, radius, slices, stacks, glm::vec4(color, 1.0f), solid, id); } - - void renderSphere(gpu::Batch& batch, float radius, int slices, int stacks, const glm::vec4& color, bool solid = true, int id = UNKNOWN_ID); + void renderSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); + void renderWireSphereInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); + void renderSphere(gpu::Batch& batch); + void renderWireSphere(gpu::Batch& batch); void renderGrid(gpu::Batch& batch, int xDivisions, int yDivisions, const glm::vec4& color); void renderGrid(gpu::Batch& batch, int x, int y, int width, int height, int rows, int cols, const glm::vec4& color, int id = UNKNOWN_ID); - void renderSolidCubeInstances(gpu::Batch& batch, size_t count, gpu::BufferPointer transformBuffer, gpu::BufferPointer colorBuffer); - void renderSolidCube(gpu::Batch& batch, float size, const glm::vec4& color); - void renderWireCube(gpu::Batch& batch, float size, const glm::vec4& color); void renderBevelCornersRect(gpu::Batch& batch, int x, int y, int width, int height, int bevelDistance, const glm::vec4& color, int id = UNKNOWN_ID); - void renderUnitCube(gpu::Batch& batch); void renderUnitQuad(gpu::Batch& batch, const glm::vec4& color = glm::vec4(1), int id = UNKNOWN_ID); void renderQuad(gpu::Batch& batch, int x, int y, int width, int height, const glm::vec4& color, int id = UNKNOWN_ID) @@ -204,30 +229,47 @@ public: void updateVertices(int id, const QVector& points, const QVector& texCoords, const glm::vec4& color); void renderVertices(gpu::Batch& batch, gpu::Primitive primitiveType, int id); - /// Loads geometry from the specified URL. - /// \param fallback a fallback URL to load if the desired one is unavailable - /// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested - QSharedPointer getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false); - /// Set a batch to the simple pipeline, returning the previous pipeline void useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend = false); private: GeometryCache(); virtual ~GeometryCache(); + void buildShapes(); typedef QPair IntPair; typedef QPair VerticesIndices; + struct ShapeData { + size_t _indexOffset{ 0 }; + size_t _indexCount{ 0 }; + size_t _wireIndexOffset{ 0 }; + size_t _wireIndexCount{ 0 }; + + gpu::BufferView _positionView; + gpu::BufferView _normalView; + gpu::BufferPointer _indices; + + void setupVertices(gpu::BufferPointer& vertexBuffer, const VertexVector& vertices); + void setupIndices(gpu::BufferPointer& indexBuffer, const IndexVector& indices, const IndexVector& wireIndices); + void setupBatch(gpu::Batch& batch) const; + void draw(gpu::Batch& batch) const; + void drawWire(gpu::Batch& batch) const; + void drawInstances(gpu::Batch& batch, size_t count) const; + void drawWireInstances(gpu::Batch& batch, size_t count) const; + }; + + using VShape = std::array; + + VShape _shapes; + + + gpu::PipelinePointer _standardDrawPipeline; gpu::PipelinePointer _standardDrawPipelineNoBlend; - QHash _cubeVerticies; - QHash _cubeColors; - gpu::BufferPointer _wireCubeIndexBuffer; - QHash _solidCubeVertices; - QHash _solidCubeColors; - gpu::BufferPointer _solidCubeIndexBuffer; + gpu::BufferPointer _shapeVertices{ std::make_shared() }; + gpu::BufferPointer _shapeIndices{ std::make_shared() }; class BatchItemDetails { public: @@ -249,7 +291,7 @@ private: QHash _coneVBOs; - int _nextID; + int _nextID{ 0 }; QHash _lastRegisteredQuad3DTexture; QHash _quad3DTextures; @@ -291,15 +333,7 @@ private: QHash _lastRegisteredAlternateGridBuffers; QHash _gridColors; - QHash _sphereVertices; - QHash _registeredSphereVertices; - QHash _lastRegisteredSphereVertices; - QHash _sphereIndices; - QHash _registeredSphereIndices; - QHash _lastRegisteredSphereIndices; - QHash _sphereColors; - QHash _registeredSphereColors; - QHash _lastRegisteredSphereColors; + QHash > _networkGeometry; }; #endif // hifi_GeometryCache_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index cae4167567..918d9005ae 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -75,7 +75,6 @@ Model::Model(RigPointer rig, QObject* parent) : _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), - _calculatedMeshPartOffsetValid(false), _calculatedMeshPartBoxesValid(false), _calculatedMeshBoxesValid(false), _calculatedMeshTrianglesValid(false), @@ -606,25 +605,6 @@ bool Model::convexHullContains(glm::vec3 point) { return false; } -void Model::recalculateMeshPartOffsets() { - if (!_calculatedMeshPartOffsetValid) { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - int numberOfMeshes = geometry.meshes.size(); - _calculatedMeshPartOffset.clear(); - for (int i = 0; i < numberOfMeshes; i++) { - const FBXMesh& mesh = geometry.meshes.at(i); - qint64 partOffset = 0; - for (int j = 0; j < mesh.parts.size(); j++) { - const FBXMeshPart& part = mesh.parts.at(j); - _calculatedMeshPartOffset[QPair(i, j)] = partOffset; - partOffset += part.quadIndices.size() * sizeof(int); - partOffset += part.triangleIndices.size() * sizeof(int); - - } - } - _calculatedMeshPartOffsetValid = true; - } -} // TODO: we seem to call this too often when things haven't actually changed... look into optimizing this // Any script might trigger findRayIntersectionAgainstSubMeshes (and maybe convexHullContains), so these // can occur multiple times. In addition, rendering does it's own ray picking in order to decide which @@ -641,8 +621,6 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { _calculatedMeshTriangles.clear(); _calculatedMeshTriangles.resize(numberOfMeshes); _calculatedMeshPartBoxes.clear(); - _calculatedMeshPartOffset.clear(); - _calculatedMeshPartOffsetValid = false; for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents); @@ -651,7 +629,6 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { if (pickAgainstTriangles) { QVector thisMeshTriangles; - qint64 partOffset = 0; for (int j = 0; j < mesh.parts.size(); j++) { const FBXMeshPart& part = mesh.parts.at(j); @@ -737,15 +714,9 @@ void Model::recalculateMeshBoxes(bool pickAgainstTriangles) { } } _calculatedMeshPartBoxes[QPair(i, j)] = thisPartBounds; - _calculatedMeshPartOffset[QPair(i, j)] = partOffset; - - partOffset += part.quadIndices.size() * sizeof(int); - partOffset += part.triangleIndices.size() * sizeof(int); - } _calculatedMeshTriangles[i] = thisMeshTriangles; _calculatedMeshPartBoxesValid = true; - _calculatedMeshPartOffsetValid = true; } } _calculatedMeshBoxesValid = true; @@ -1475,12 +1446,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, int shape return; // bail asap } - // We need to make sure we have valid offsets calculated before we can render - if (!_calculatedMeshPartOffsetValid) { - _mutex.lock(); - recalculateMeshPartOffsets(); - _mutex.unlock(); - } auto textureCache = DependencyManager::get(); gpu::Batch& batch = *(args->_batch); @@ -1729,32 +1694,12 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, int shape } } - qint64 offset; - { - // FIXME_STUTTER: We should n't have any lock here - _mutex.lock(); - offset = _calculatedMeshPartOffset[QPair(meshIndex, partIndex)]; - _mutex.unlock(); - } - - if (part.quadIndices.size() > 0) { - batch.setIndexBuffer(gpu::UINT32, part.getTrianglesForQuads(), 0); - batch.drawIndexed(gpu::TRIANGLES, part.trianglesForQuadsIndicesCount, 0); - - offset += part.quadIndices.size() * sizeof(int); - batch.setIndexBuffer(gpu::UINT32, (networkMesh._indexBuffer), 0); // restore this in case there are triangles too - } - - if (part.triangleIndices.size() > 0) { - batch.drawIndexed(gpu::TRIANGLES, part.triangleIndices.size(), offset); - offset += part.triangleIndices.size() * sizeof(int); - } + batch.setIndexBuffer(gpu::UINT32, part.getMergedTriangles(), 0); + batch.drawIndexed(gpu::TRIANGLES, part.mergedTrianglesIndicesCount, 0); if (args) { const int INDICES_PER_TRIANGLE = 3; - const int INDICES_PER_QUAD = 4; - args->_details._trianglesRendered += part.triangleIndices.size() / INDICES_PER_TRIANGLE; - args->_details._quadsRendered += part.quadIndices.size() / INDICES_PER_QUAD; + args->_details._trianglesRendered += part.mergedTrianglesIndicesCount / INDICES_PER_TRIANGLE; } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index acc419a81f..d13313abc8 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -353,9 +353,6 @@ private: }; QHash, AABox> _calculatedMeshPartBoxes; // world coordinate AABoxes for all sub mesh part boxes - QHash, qint64> _calculatedMeshPartOffset; - bool _calculatedMeshPartOffsetValid; - bool _calculatedMeshPartBoxesValid; QVector _calculatedMeshBoxes; // world coordinate AABoxes for all sub mesh boxes @@ -366,7 +363,6 @@ private: QMutex _mutex; void recalculateMeshBoxes(bool pickAgainstTriangles = false); - void recalculateMeshPartOffsets(); void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes diff --git a/libraries/shared/src/RenderArgs.h b/libraries/shared/src/RenderArgs.h index 7a5daf7a17..25eed96490 100644 --- a/libraries/shared/src/RenderArgs.h +++ b/libraries/shared/src/RenderArgs.h @@ -43,7 +43,6 @@ public: int _materialSwitches = 0; int _trianglesRendered = 0; - int _quadsRendered = 0; Item _opaque; Item _translucent; diff --git a/tests/gpu-test/src/main.cpp b/tests/gpu-test/src/main.cpp index b27d10e312..ad9ed9bb4a 100644 --- a/tests/gpu-test/src/main.cpp +++ b/tests/gpu-test/src/main.cpp @@ -10,15 +10,22 @@ #include #include +#include #include +#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include #include @@ -26,17 +33,17 @@ #include #include -#include -#include -#include -#include -#include -#include +// Must come after GL headers +#include + +#include #include #include +#include +#include -#include "simple_frag.h" -#include "simple_vert.h" +#include "unlit_frag.h" +#include "unlit_vert.h" class RateCounter { std::vector times; @@ -74,173 +81,7 @@ public: } }; -#define MOVE_PARAM(name) decltype(name) && name - -struct BasicModel { - gpu::PipelinePointer pipeline; -// gpu::BufferPointer vertexBuffer; -// gpu::BufferPointer indexBuffer; -// gpu::BufferPointer normalBuffer; - - gpu::BufferView vertices; - gpu::BufferView normals; - gpu::BufferPointer indices; - - gpu::Stream::FormatPointer format; - - BasicModel (MOVE_PARAM(pipeline), MOVE_PARAM(vertices), MOVE_PARAM(normals), MOVE_PARAM(indices), MOVE_PARAM(format)) - : pipeline(pipeline), vertices(vertices), normals(normals), indices(indices), format(format) {} - -// BasicModel (gpu::PipelinePointer && pipeline, gpu::BufferPointer && buffer, gpu::Stream::FormatPointer && format) -// : pipeline(pipeline), buffer(buffer), format(format) {} -}; -typedef std::shared_ptr BasicModelPointer; -#undef MOVE_PARAM - -BasicModelPointer makeCube () { - // Axis-aligned cube, facing the user at +z - // coords == binary mapping of each index, with z inverted (front face faces camera, - // instead of away from the camera) - // - // -x,+y,-z ----------- +x,+y,-z - // ___--- | ___--- | - // -x,+y,+z --------- +x,+y,+z | - // | | | | - // | | | | - // | | | | - // | | | | - // | -x,-y,-z ------|---- +x,-y,-z - // | ___--- | ___---- - // -x,-y,+z --------- +x,-y,+z - // - float s = 1.0f; - const glm::vec3 raw_verts[8] = { - // x, y, z - { -s, -s, +s }, // 0b000 0x0 - { +s, -s, +s }, // 0b001 0x1 - { -s, +s, +s }, // 0b010 0x2 - { +s, +s, +s }, // 0b011 0x3 - { -s, -s, -s }, // 0b100 0x4 - { +s, -s, -s }, // 0b101 0x5 - { -s, +s, -s }, // 0b110 0x6 - { +s, +s, -s } // 0b111 0x7 - }; - const glm::vec3 raw_normals[6] = { - { 0.0f, 0.0f, +1.0f }, // x > 0: 1, 3, 5, 7 (N 0) - { 0.0f, 0.0f, -1.0f }, // x < 0: 0, 2, 4, 6 (N 1) - { 0.0f, +1.0f, 0.0f }, // y > 0: 2, 3, 6, 7 (N 2) - { 0.0f, -1.0f, 0.0f }, // y < 0: 0, 1, 4, 5 (N 3) - { +1.0f, 0.0f, 0.0f }, // z > 0: 0, 1, 2, 3 (N 4) - { -1.0f, 0.0f, 0.0f } // z < 0: 4, 5, 6, 7 (N 5) - }; - - const glm::vec3 cube_verts[24] = { - raw_verts[1], raw_verts[3], raw_verts[5], raw_verts[7], - raw_verts[0], raw_verts[2], raw_verts[4], raw_verts[6], - raw_verts[2], raw_verts[3], raw_verts[6], raw_verts[7], - raw_verts[0], raw_verts[1], raw_verts[4], raw_verts[5], - raw_verts[0], raw_verts[1], raw_verts[2], raw_verts[3], - raw_verts[4], raw_verts[5], raw_verts[6], raw_verts[7] - }; - const glm::vec3 cube_normals[24] = { - raw_normals[0], raw_normals[0], raw_normals[0], raw_normals[0], - raw_normals[1], raw_normals[1], raw_normals[1], raw_normals[1], - raw_normals[2], raw_normals[2], raw_normals[2], raw_normals[2], - raw_normals[3], raw_normals[3], raw_normals[3], raw_normals[3], - raw_normals[4], raw_normals[4], raw_normals[4], raw_normals[4], - raw_normals[5], raw_normals[5], raw_normals[5], raw_normals[5] - }; - - int16_t cube_indices_tris[36]; - for (int i = 0, k = 0; i < 36; k += 4) { - cube_indices_tris[i++] = k + 0; - cube_indices_tris[i++] = k + 3; - cube_indices_tris[i++] = k + 1; - cube_indices_tris[i++] = k + 0; - cube_indices_tris[i++] = k + 2; - cube_indices_tris[i++] = k + 3; - } - -// const int16_t cube_indices_tris[36] { -// 0, 3, 1, 0, 2, 3, -// }; - -// const glm::vec3 cube_normals[] = { -// { 0.0f, 0.0f, 1.0f }, -// { 0.0f, 0.0f, 1.0f }, -// { 0.0f, 0.0f, 1.0f }, -// { 0.0f, 0.0f, 1.0f }, -// { -1.0f, 0.0f, 0.0f }, -// { -1.0f, 0.0f, 0.0f }, -// { -1.0f, 0.0f, 0.0f }, -// { -1.0f, 0.0f, 0.0f }, -// }; -// const int16_t cube_indices[] = { -// 3, 1, 0, 2, 3, 0, -// 6, 2, 0, 4, 6, 0, -// }; - - gpu::Stream::FormatPointer format = std::make_shared(); - - assert(gpu::Stream::POSITION == 0 && gpu::Stream::NORMAL == 1); - const int BUFFER_SLOT = 0; - - format->setAttribute(gpu::Stream::POSITION, BUFFER_SLOT, gpu::Element::VEC3F_XYZ); - format->setAttribute(gpu::Stream::NORMAL, BUFFER_SLOT, gpu::Element::VEC3F_XYZ); - - auto vertexBuffer = std::make_shared(24 * sizeof(glm::vec3), (gpu::Byte*)cube_verts); - auto normalBuffer = std::make_shared(24 * sizeof(glm::vec3), (gpu::Byte*)cube_normals); - gpu::BufferPointer indexBuffer = std::make_shared(36 * sizeof(int16_t), (gpu::Byte*)cube_indices_tris); - - auto positionElement = format->getAttributes().at(gpu::Stream::POSITION)._element; - auto normalElement = format->getAttributes().at(gpu::Stream::NORMAL)._element; - - gpu::BufferView vertexView { vertexBuffer, positionElement }; - gpu::BufferView normalView { normalBuffer, normalElement }; - - // Create shaders - auto vs = gpu::ShaderPointer(gpu::Shader::createVertex({ simple_vert })); - auto fs = gpu::ShaderPointer(gpu::Shader::createPixel({ simple_frag })); - auto shader = gpu::ShaderPointer(gpu::Shader::createProgram(vs, fs)); - - gpu::Shader::BindingSet bindings; - bindings.insert({ "lightPosition", 1 }); - - if (!gpu::Shader::makeProgram(*shader, bindings)) { - printf("Could not compile shader\n"); - if (!vs) - printf("bad vertex shader\n"); - if (!fs) - printf("bad fragment shader\n"); - if (!shader) - printf("bad shader program\n"); - exit(-1); - } - - auto state = std::make_shared(); -// state->setAntialiasedLineEnable(true); - state->setMultisampleEnable(true); - state->setDepthTest({ true }); - auto pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); - - return std::make_shared( - std::move(pipeline), - std::move(vertexView), - std::move(normalView), - std::move(indexBuffer), - std::move(format) - ); -} -void renderCube(gpu::Batch & batch, const BasicModel & cube) { - - batch.setPipeline(cube.pipeline); - batch.setInputFormat(cube.format); - batch.setInputBuffer(gpu::Stream::POSITION, cube.vertices); - batch.setInputBuffer(gpu::Stream::NORMAL, cube.normals); - batch.setIndexBuffer(gpu::INT16, cube.indices, 0); -// batch.drawIndexed(gpu::TRIANGLES, 12); - batch.draw(gpu::TRIANGLES, 24); -} +uint32_t toCompactColor(const glm::vec4& color); gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::string & fragmentShaderSrc, const gpu::Shader::BindingSet & bindings) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(vertexShaderSrc)); @@ -253,6 +94,14 @@ gpu::ShaderPointer makeShader(const std::string & vertexShaderSrc, const std::st return shader; } +float getSeconds(quint64 start = 0) { + auto usecs = usecTimestampNow() - start; + auto msecs = usecs / USECS_PER_MSEC; + float seconds = (float)msecs / MSECS_PER_SECOND; + return seconds; +} + + // Creates an OpenGL window that renders a simple unlit scene using the gpu library and GeometryCache // Should eventually get refactored into something that supports multiple gpu backends. @@ -265,9 +114,9 @@ class QTestWindow : public QWindow { gpu::ContextPointer _context; gpu::PipelinePointer _pipeline; glm::mat4 _projectionMatrix; -// BasicModelPointer _cubeModel; RateCounter fps; QTime _time; + int _instanceLocation{ -1 }; protected: void renderText(); @@ -288,6 +137,7 @@ public: format.setVersion(4, 1); format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); format.setOption(QSurfaceFormat::DebugContext); + format.setSwapInterval(0); setFormat(format); @@ -301,23 +151,21 @@ public: gpu::Context::init(); _context = std::make_shared(); - auto shader = makeShader(simple_vert, simple_frag, gpu::Shader::BindingSet {}); + auto shader = makeShader(unlit_vert, unlit_frag, gpu::Shader::BindingSet{}); auto state = std::make_shared(); state->setMultisampleEnable(true); state->setDepthTest(gpu::State::DepthTest { true }); _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(shader, state)); - + _instanceLocation = _pipeline->getProgram()->getUniforms().findLocation("Instanced"); // Clear screen gpu::Batch batch; batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 1.0, 0.0, 0.5, 1.0 }); _context->render(batch); -// _cubeModel = makeCube(); - DependencyManager::set(); + DependencyManager::set(); - setFramePosition(QPoint(-1000, 0)); resize(QSize(800, 600)); _time.start(); @@ -327,6 +175,8 @@ public: } void draw() { + static auto startTime = usecTimestampNow(); + if (!isVisible()) { return; } @@ -342,37 +192,81 @@ public: glm::vec3 unitscale { 1.0f }; glm::vec3 up { 0.0f, 1.0f, 0.0f }; - glm::vec3 cam_pos { 1.5f * sinf(t), 0.0f, 2.0f }; -// glm::vec3 camera_focus { 5.0f * cosf(t * 0.1f), 0.0f, 0.0f }; - glm::vec3 camera_focus { 0.0f, 0.0f, 0.0f }; - glm::quat cam_rotation; - // glm::quat cam_rotation = glm::quat_cast(glm::lookAt(cam_pos, camera_focus, up)); - // cam_rotation.w = -cam_rotation.w; - // printf("cam rotation: %f %f %f %f\n", cam_rotation.x, cam_rotation.y, cam_rotation.z, cam_rotation.w); - Transform cam_transform { cam_rotation, unitscale, cam_pos }; - - batch.setViewTransform(cam_transform); + glm::vec3 camera_position { 1.5f * sinf(t), 0.0f, 1.5f * cos(t) }; + + static const vec3 camera_focus(0); + static const vec3 camera_up(0, 1, 0); + glm::mat4 camera = glm::inverse(glm::lookAt(camera_position, camera_focus, up)); + batch.setViewTransform(camera); batch.setPipeline(_pipeline); - + batch.setModelTransform(Transform()); + auto geometryCache = DependencyManager::get(); // Render grid on xz plane (not the optimal way to do things, but w/e) // Note: GeometryCache::renderGrid will *not* work, as it is apparenly unaffected by batch rotations and renders xy only - batch.setModelTransform(Transform()); + static const std::string GRID_INSTANCE = "Grid"; + static auto compactColor1 = toCompactColor(vec4{ 0.35f, 0.25f, 0.15f, 1.0f }); + static auto compactColor2 = toCompactColor(vec4{ 0.15f, 0.25f, 0.35f, 1.0f }); + auto transformBuffer = batch.getNamedBuffer(GRID_INSTANCE, 0); + auto colorBuffer = batch.getNamedBuffer(GRID_INSTANCE, 1); for (int i = 0; i < 100; ++i) { - geometryCache->renderLine(batch, { -100.0f, -1.0f, -50.0f + float(i) }, { 100.0f, -1.0f, -50.0f + float(i) }, { 0.35f, 0.25f, 0.15f, 1.0f }); + { + glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); + transform = glm::scale(transform, vec3(100, 1, 1)); + transformBuffer->append(transform); + colorBuffer->append(compactColor1); + } + + { + glm::mat4 transform = glm::mat4_cast(quat(vec3(0, PI / 2.0f, 0))); + transform = glm::translate(transform, vec3(0, -1, -50 + i)); + transform = glm::scale(transform, vec3(100, 1, 1)); + transformBuffer->append(transform); + colorBuffer->append(compactColor2); + } } - for (int i = 0; i < 100; ++i) { - geometryCache->renderLine(batch, { -50.0f + float(i), -1.0f, -100.0f}, { -50.0f + float(i), -1.0f, 100.0f }, { 0.15f, 0.25f, 0.35f, 1.0f }); - } - + + batch.setupNamedCalls(GRID_INSTANCE, 200, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { + batch.setViewTransform(camera); + batch.setModelTransform(Transform()); + batch.setPipeline(_pipeline); + auto& xfm = data._buffers[0]; + auto& color = data._buffers[1]; + batch._glUniform1i(_instanceLocation, 1); + geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data._count, xfm, color); + batch._glUniform1i(_instanceLocation, 0); + }); + + + // Render unlit cube + sphere - geometryCache->renderUnitCube(batch); - geometryCache->renderWireCube(batch, 1.0f, { 0.4f, 0.4f, 0.7f, 1.0f }); - - batch.setModelTransform(Transform().setTranslation({ 1.5f, -0.5f, -0.5f })); - geometryCache->renderSphere(batch, 0.5f, 50, 50, { 0.8f, 0.25f, 0.25f }); + + static GeometryCache::Shape SHAPE[] = { + GeometryCache::Cube, + GeometryCache::Sphere, + GeometryCache::Tetrahedron, + GeometryCache::Icosahedron, + }; + + static auto startUsecs = usecTimestampNow(); + float seconds = getSeconds(startUsecs); + seconds /= 4.0; + int shapeIndex = ((int)seconds) % 4; + bool wire = seconds - floor(seconds) > 0.5f; + batch.setModelTransform(Transform()); + batch._glColor4f(0.8f, 0.25f, 0.25f, 1.0f); + + if (wire) { + geometryCache->renderWireShape(batch, SHAPE[shapeIndex]); + } else { + geometryCache->renderShape(batch, SHAPE[shapeIndex]); + } + batch.setModelTransform(Transform().setScale(1.05f)); + batch._glColor4f(1, 1, 1, 1); + geometryCache->renderWireCube(batch); + _context->render(batch); _qGlContext->swapBuffers(this); diff --git a/tests/gpu-test/src/simple.slf b/tests/gpu-test/src/unlit.slf similarity index 94% rename from tests/gpu-test/src/simple.slf rename to tests/gpu-test/src/unlit.slf index 31d33a73e4..350190180a 100644 --- a/tests/gpu-test/src/simple.slf +++ b/tests/gpu-test/src/unlit.slf @@ -20,7 +20,6 @@ in vec3 _normal; in vec3 _color; void main(void) { - Material material = getMaterial(); packDeferredFragment( normalize(_normal.xyz), glowIntensity, diff --git a/tests/gpu-test/src/simple.slv b/tests/gpu-test/src/unlit.slv similarity index 64% rename from tests/gpu-test/src/simple.slv rename to tests/gpu-test/src/unlit.slv index 99f404eaec..3271d0ee90 100644 --- a/tests/gpu-test/src/simple.slv +++ b/tests/gpu-test/src/unlit.slv @@ -19,6 +19,7 @@ <$declareStandardTransform()$> // the interpolated normal +uniform bool Instanced = false; out vec3 _normal; out vec3 _color; @@ -31,6 +32,12 @@ void main(void) { // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + if (Instanced) { + <$transformInstancedModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformInstancedModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + } else { + <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> + <$transformModelToEyeDir(cam, obj, inNormal.xyz, _normal)$> + } + _normal = vec3(0.0, 0.0, 1.0); } \ No newline at end of file