void VoxelTree::createSphere(float radius, float xc, float yc, float zc, float voxelSize, bool solid, creationMode mode, bool destructive, bool debug) { bool wantColorRandomizer = (mode == RANDOM); bool wantNaturalSurface = (mode == NATURAL); bool wantNaturalColor = (mode == NATURAL); // About the color of the sphere... we're going to make this sphere be a mixture of two colors // in NATURAL mode, those colors will be green dominant and blue dominant. In GRADIENT mode we // will randomly pick which color family red, green, or blue to be dominant. In RANDOM mode we // ignore these dominant colors and make every voxel a completely random color. unsigned char r1, g1, b1, r2, g2, b2; if (wantNaturalColor) { r1 = r2 = b2 = g1 = 0; b1 = g2 = 255; } else if (!wantColorRandomizer) { unsigned char dominantColor1 = randIntInRange(1, 3); //1=r, 2=g, 3=b dominant unsigned char dominantColor2 = randIntInRange(1, 3); if (dominantColor1 == dominantColor2) { dominantColor2 = dominantColor1 + 1 % 3; } r1 = (dominantColor1 == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100); g1 = (dominantColor1 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100); b1 = (dominantColor1 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100); r2 = (dominantColor2 == 1) ? randIntInRange(200, 255) : randIntInRange(40, 100); g2 = (dominantColor2 == 2) ? randIntInRange(200, 255) : randIntInRange(40, 100); b2 = (dominantColor2 == 3) ? randIntInRange(200, 255) : randIntInRange(40, 100); } // We initialize our rgb to be either "grey" in case of randomized surface, or // the average of the gradient, in the case of the gradient sphere. unsigned char red = wantColorRandomizer ? 128 : (r1 + r2) / 2; // average of the colors unsigned char green = wantColorRandomizer ? 128 : (g1 + g2) / 2; unsigned char blue = wantColorRandomizer ? 128 : (b1 + b2) / 2; // I want to do something smart like make these inside circles with bigger voxels, but this doesn't seem to work. float thisVoxelSize = voxelSize; // radius / 2.0f; float thisRadius = 0.0; if (!solid) { thisRadius = radius; // just the outer surface thisVoxelSize = voxelSize; } // If you also iterate form the interior of the sphere to the radius, making // larger and larger spheres you'd end up with a solid sphere. And lots of voxels! bool lastLayer = false; while (!lastLayer) { lastLayer = (thisRadius + (voxelSize * 2.0) >= radius); // We want to make sure that as we "sweep" through our angles we use a delta angle that voxelSize // small enough to not skip any voxels we can calculate theta from our desired arc length // lenArc = ndeg/360deg * 2pi*R ---> lenArc = theta/2pi * 2pi*R // lenArc = theta*R ---> theta = lenArc/R ---> theta = g/r float angleDelta = (thisVoxelSize / thisRadius); if (debug) { int percentComplete = 100 * (thisRadius/radius); qDebug("percentComplete=%d\n",percentComplete); } for (float theta=0.0; theta <= 2 * M_PI; theta += angleDelta) { for (float phi=0.0; phi <= M_PI; phi += angleDelta) { bool naturalSurfaceRendered = false; float x = xc + thisRadius * cos(theta) * sin(phi); float y = yc + thisRadius * sin(theta) * sin(phi); float z = zc + thisRadius * cos(phi); // if we're on the outer radius, then we do a couple of things differently. // 1) If we're in NATURAL mode we will actually draw voxels from our surface outward (from the surface) up // some random height. This will give our sphere some contours. // 2) In all modes, we will use our "outer" color to draw the voxels. Otherwise we will use the average color if (lastLayer) { if (false && debug) { qDebug("adding candy shell: theta=%f phi=%f thisRadius=%f radius=%f\n", theta, phi, thisRadius,radius); } switch (mode) { case RANDOM: { red = randomColorValue(165); green = randomColorValue(165); blue = randomColorValue(165); } break; case GRADIENT: { float gradient = (phi / M_PI); red = r1 + ((r2 - r1) * gradient); green = g1 + ((g2 - g1) * gradient); blue = b1 + ((b2 - b1) * gradient); } break; case NATURAL: { glm::vec3 position = glm::vec3(theta,phi,radius); float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f) + .125f * glm::perlin(position * 16.f); float gradient = (1.0f + perlin)/ 2.0f; red = (unsigned char)std::min(255, std::max(0, (int)(r1 + ((r2 - r1) * gradient)))); green = (unsigned char)std::min(255, std::max(0, (int)(g1 + ((g2 - g1) * gradient)))); blue = (unsigned char)std::min(255, std::max(0, (int)(b1 + ((b2 - b1) * gradient)))); if (debug) { qDebug("perlin=%f gradient=%f color=(%d,%d,%d)\n",perlin, gradient, red, green, blue); } } break; } if (wantNaturalSurface) { // for natural surfaces, we will render up to 16 voxel's above the surface of the sphere glm::vec3 position = glm::vec3(theta,phi,radius); float perlin = glm::perlin(position) + .25f * glm::perlin(position * 4.f) + .125f * glm::perlin(position * 16.f); float gradient = (1.0f + perlin)/ 2.0f; int height = (4 * gradient)+1; // make it at least 4 thick, so we get some averaging float subVoxelScale = thisVoxelSize; for (int i = 0; i < height; i++) { x = xc + (thisRadius + i * subVoxelScale) * cos(theta) * sin(phi); y = yc + (thisRadius + i * subVoxelScale) * sin(theta) * sin(phi); z = zc + (thisRadius + i * subVoxelScale) * cos(phi); this->createVoxel(x, y, z, subVoxelScale, red, green, blue, destructive); } naturalSurfaceRendered = true; } } if (!naturalSurfaceRendered) { this->createVoxel(x, y, z, thisVoxelSize, red, green, blue, destructive); } } } thisRadius += thisVoxelSize; thisVoxelSize = std::max(voxelSize, thisVoxelSize / 2.0f); } }
void draw() { // Attempting to draw before we're visible and have a valid size will // produce GL errors. if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) { return; } makeCurrent(); gpu::Batch batch; batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.0f, 0.0f, 1.0f }); batch.clearDepthFramebuffer(1e4); batch.setViewportTransform({ 0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio() }); batch.setProjectionTransform(_projectionMatrix); float t = _time.elapsed() * 1e-3f; glm::vec3 unitscale { 1.0f }; glm::vec3 up { 0.0f, 1.0f, 0.0f }; float distance = 3.0f; glm::vec3 camera_position{ distance * sinf(t), 0.0f, distance * cosf(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<GeometryCache>(); // 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 { 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 }); static std::vector<glm::mat4> transforms; static gpu::BufferPointer colorBuffer; if (!transforms.empty()) { transforms.reserve(200); colorBuffer = std::make_shared<gpu::Buffer>(); for (int i = 0; i < 100; ++i) { { glm::mat4 transform = glm::translate(mat4(), vec3(0, -1, -50 + i)); transform = glm::scale(transform, vec3(100, 1, 1)); transforms.push_back(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)); transforms.push_back(transform); colorBuffer->append(compactColor2); } } } auto pipeline = geometryCache->getSimplePipeline(); for (auto& transform : transforms) { batch.setModelTransform(transform); batch.setupNamedCalls(GRID_INSTANCE, [=](gpu::Batch& batch, gpu::Batch::NamedBatchData& data) { batch.setViewTransform(camera); batch.setPipeline(_pipeline); geometryCache->renderWireShapeInstances(batch, GeometryCache::Line, data.count(), colorBuffer); }); } } { static const size_t ITEM_COUNT = 1000; static const float SHAPE_INTERVAL = (PI * 2.0f) / ITEM_COUNT; static const float ITEM_INTERVAL = SHAPE_INTERVAL / TYPE_COUNT; 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 std::vector<Transform> transforms; static std::vector<vec4> colors; static gpu::BufferPointer indirectBuffer; static gpu::BufferPointer transformBuffer; static gpu::BufferPointer colorBuffer; static gpu::BufferView colorView; static gpu::BufferView instanceXfmView; if (!transformBuffer) { transformBuffer = std::make_shared<gpu::Buffer>(); colorBuffer = std::make_shared<gpu::Buffer>(); indirectBuffer = std::make_shared<gpu::Buffer>(); static const float ITEM_RADIUS = 20; static const vec3 ITEM_TRANSLATION{ 0, 0, -ITEM_RADIUS }; for (size_t i = 0; i < TYPE_COUNT; ++i) { GeometryCache::Shape shape = SHAPE[i]; GeometryCache::ShapeData shapeData = geometryCache->_shapes[shape]; { gpu::Batch::DrawIndexedIndirectCommand indirectCommand; indirectCommand._count = (uint)shapeData._indexCount; indirectCommand._instanceCount = ITEM_COUNT; indirectCommand._baseInstance = (uint)(i * ITEM_COUNT); indirectCommand._firstIndex = (uint)shapeData._indexOffset / 2; indirectCommand._baseVertex = 0; indirectBuffer->append(indirectCommand); } //indirectCommand._count float startingInterval = ITEM_INTERVAL * i; for (size_t j = 0; j < ITEM_COUNT; ++j) { float theta = j * SHAPE_INTERVAL + startingInterval; auto transform = glm::rotate(mat4(), theta, Vectors::UP); transform = glm::rotate(transform, (randFloat() - 0.5f) * PI / 4.0f, Vectors::UNIT_X); transform = glm::translate(transform, ITEM_TRANSLATION); transform = glm::scale(transform, vec3(randFloat() / 2.0f + 0.5f)); transformBuffer->append(transform); transforms.push_back(transform); auto color = vec4{ randomColorValue(64), randomColorValue(64), randomColorValue(64), 255 }; color /= 255.0f; colors.push_back(color); colorBuffer->append(toCompactColor(color)); } } colorView = gpu::BufferView(colorBuffer, COLOR_ELEMENT); instanceXfmView = gpu::BufferView(transformBuffer, TRANSFORM_ELEMENT); } #if 1 GeometryCache::ShapeData shapeData = geometryCache->_shapes[GeometryCache::Icosahedron]; { batch.setViewTransform(camera); batch.setModelTransform(Transform()); batch.setPipeline(_pipeline); batch.setInputFormat(getInstancedSolidStreamFormat()); batch.setInputBuffer(gpu::Stream::COLOR, colorView); batch.setIndirectBuffer(indirectBuffer); shapeData.setupBatch(batch); batch.multiDrawIndexedIndirect(TYPE_COUNT, gpu::TRIANGLES); } #else batch.setViewTransform(camera); batch.setPipeline(_pipeline); for (size_t i = 0; i < TYPE_COUNT; ++i) { GeometryCache::Shape shape = SHAPE[i]; for (size_t j = 0; j < ITEM_COUNT; ++j) { int index = i * ITEM_COUNT + j; batch.setModelTransform(transforms[index]); const vec4& color = colors[index]; batch._glColor4f(color.r, color.g, color.b, 1.0); geometryCache->renderShape(batch, shape); } } #endif } // Render unlit cube + sphere static auto startUsecs = usecTimestampNow(); float seconds = getSeconds(startUsecs); seconds /= 4.0f; int shapeIndex = ((int)seconds) % TYPE_COUNT; bool wire = (seconds - floorf(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(2.05f)); batch._glColor4f(1, 1, 1, 1); geometryCache->renderWireCube(batch); _context->render(batch); _qGlContext.swapBuffers(this); fps.increment(); if (fps.elapsed() >= 0.5f) { qDebug() << "FPS: " << fps.rate(); fps.reset(); } }