Skip to content

Commit

Permalink
Merge pull request #52 from adshares/develop
Browse files Browse the repository at this point in the history
Add model ads (#51)
  • Loading branch information
m-pilarczyk authored Feb 23, 2022
2 parents bc5ee55 + a3d318c commit fbfe90c
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 2 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.1] - 2022-02-23
### Added
- Model ads support

## [0.4.0] - 2022-02-16
### Added
- Video ads support
Expand Down Expand Up @@ -54,7 +58,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changelog, Readme, License
- Project structure

[Unreleased]: https://github.com/adshares/adclassify/compare/v0.4.0...HEAD
[Unreleased]: https://github.com/adshares/adclassify/compare/v0.4.1...HEAD
[0.4.1]: https://github.com/adshares/adselect/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/adshares/adselect/compare/v0.3.2...v0.4.0
[0.3.2]: https://github.com/adshares/adselect/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/adshares/adselect/compare/v0.3.0...v0.3.1
Expand Down
1 change: 1 addition & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '@fortawesome/fontawesome-free/css/regular.css'
import '@fortawesome/fontawesome-free/css/solid.css'
import './button-checkbox'
import './panel'
import './model-preview'

$(document).ready(function () {
$('[data-toggle="popover"]').popover()
Expand Down
192 changes: 192 additions & 0 deletions assets/js/model-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import $ from 'jquery'
import * as THREE from 'three'
import {RoomEnvironment} from 'three/examples/jsm/environments/RoomEnvironment'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
import {VOXLoader, VOXMesh} from 'three/examples/jsm/loaders/VOXLoader'
import {MeshoptDecoder} from 'three/examples/jsm/libs/meshopt_decoder.module'

function extractModelUrl(element) {
return element.removeAttributeNode(element.getAttributeNode('data-src')).value
}

function scaleUniformlyTo2x2x2(box) {
return 1 / Math.max(
Math.abs(box.min.x),
Math.abs(box.min.y),
Math.abs(box.min.z),
box.max.x,
box.max.y,
box.max.z,
)
}

function initCamera() {
const camera = new THREE.PerspectiveCamera(50, 1, 0.01, 10)
camera.position.set(1.75, 0.75, 1.75)
return camera
}

function initRenderer(element) {
const renderer = new THREE.WebGLRenderer({antialias: true})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(element.offsetWidth, element.offsetHeight)
return renderer;
}

function initScene() {
const scene = new THREE.Scene()
scene.background = new THREE.Color(0xbbbbbb)
scene.add(axes(1.0))
scene.add(frame(2.0))
return scene
}

function initControls(camera, renderer) {
const controls = new OrbitControls(camera, renderer.domElement)
controls.minDistance = 1.5
controls.maxDistance = 4.5
controls.autoRotate = true
controls.autoRotateSpeed = 10
return controls
}

function axes(length) {
const points = []
points.push(new THREE.Vector3(length, 0, 0))
points.push(new THREE.Vector3(0, 0, 0))
points.push(new THREE.Vector3(0, length, 0))
points.push(new THREE.Vector3(0, 0, 0))
points.push(new THREE.Vector3(0, 0, length))
const geometry = new THREE.BufferGeometry().setFromPoints(points)
const material = new THREE.LineBasicMaterial({color: 0x0000ff})
return new THREE.Line(geometry, material)
}

function frame(length) {
const geometry = new THREE.BoxGeometry(length, length, length)
const material = new THREE.LineBasicMaterial({color: 0x00ffff})
return new THREE.Line(geometry, material)
}

function handleLoadError(element, error) {
console.error(error)
const message = error.message ? `Error during model load: ${error.message}` : 'Model load failed'
const spanElement = document.createElement('span')
spanElement.innerHTML = message
element.appendChild(spanElement)
}

function displayGltfModel(element) {
let mixer
const modelUrl = extractModelUrl(element)

const clock = new THREE.Clock()
const renderer = initRenderer(element);
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.toneMappingExposure = 1
renderer.outputEncoding = THREE.sRGBEncoding

const camera = initCamera()
const scene = initScene()
const environment = new RoomEnvironment()
const pmremGenerator = new THREE.PMREMGenerator(renderer)
scene.environment = pmremGenerator.fromScene(environment).texture
const controls = initControls(camera, renderer)

const loader = new GLTFLoader()
loader.setMeshoptDecoder(MeshoptDecoder)
loader.load(modelUrl, gltf => {
const model = gltf.scene
const boundingBox = new THREE.Box3().setFromObject(model)
model.scale.setScalar(scaleUniformlyTo2x2x2(boundingBox))
scene.add(model)

if (gltf.animations.length > 0) {
mixer = new THREE.AnimationMixer(model)
mixer.clipAction(gltf.animations[0]).play()
}
element.appendChild(renderer.domElement)
animate()
}, () => {
}, error => {
handleLoadError(element, error);
})

function animate() {
requestAnimationFrame(animate)
if (mixer !== undefined) {
const delta = clock.getDelta()
mixer.update(delta)
}
controls.update()
renderer.render(scene, camera)
}
}

function displayVoxModel(element) {
const modelUrl = extractModelUrl(element)
const renderer = initRenderer(element)
const camera = initCamera()
const scene = initScene()
const controls = initControls(camera, renderer)

const hemisphereLight = new THREE.HemisphereLight(0x888888, 0x444444, 1)
scene.add(hemisphereLight)
const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.75)
directionalLight1.position.set(1.5, 3, 2.5)
scene.add(directionalLight1)
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5)
directionalLight2.position.set(-1.5, -3, -2.5)
scene.add(directionalLight2)

const loader = new VOXLoader()
loader.load(modelUrl, chunks => {
const group = new THREE.Group()
const boundingBox = new THREE.Box3()
const palette = chunks[chunks.length - 1].palette
chunks.forEach(chunk => {
chunk.palette = palette
const mesh = new VOXMesh(chunk)
boundingBox.expandByObject(mesh)
mesh.visible = false
group.add(mesh)
})
group.name = 'model'
group.scale.setScalar(scaleUniformlyTo2x2x2(boundingBox))
scene.add(group)

element.appendChild(renderer.domElement)
requestAnimationFrame(animate)
}, () => {
}, error => {
handleLoadError(element, error);
})

function animate(time) {
requestAnimationFrame(animate)
const model = scene.getObjectByName('model')
if (model !== undefined) {
const framesCount = model.children.length
const periodPerFrame = 150
const period = framesCount * periodPerFrame
const currentTime = time % period
const currentFrameIndex = Math.floor(currentTime / periodPerFrame)
model.children.forEach((frame, index) => frame.visible = index === currentFrameIndex)
}

controls.update()
renderer.render(scene, camera)
}
}

$(document).ready(function () {
for (let element of document.getElementsByClassName('model-preview')) {
const mime = element.getAttributeNode('data-mime').value
if (mime === 'model/voxel') {
displayVoxModel(element)
} else if (mime === 'model/gltf-binary') {
displayGltfModel(element)
}
}
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"node-sass": "^7.0",
"popper.js": "^1.15",
"sass-loader": "^12.0",
"three": "^0.137.5",
"webpack": "^5.0",
"webpack-notifier": "^1.8"
},
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private function validBanner(array $banner): void
throw new UnprocessableEntityHttpException(sprintf('Invalid banner checksum (in %s)', $banner['id']));
}

if (empty($banner['type']) || !in_array($banner['type'], ['image', 'html', 'direct', 'video'])) {
if (empty($banner['type']) || !in_array($banner['type'], ['image', 'html', 'direct', 'video', 'model'])) {
throw new UnprocessableEntityHttpException(sprintf('Invalid banner type (in %s)', $banner['id']));
}

Expand Down
6 changes: 6 additions & 0 deletions templates/classification/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@
<code><a href="{{ directUrl }}" target="_blank" rel="nofollow noreferrer noopener">{{ directUrl }}</a></code>
<br />
<code>{% if request.ad.size matches '/^\\d+x\\d+$/' %}iframe {% endif %}{{ request.ad.size }}</code>
{% elseif request.type == 'model' %}
<div class="model-preview"
data-src="{{ request|video64(true) }}"
data-mime="{{ request.ad.mime }}"
style="width: 300px; height: 300px; background-color: #ed969e">
</div>
{% else %}
<div style="
width: {{ request.ad.width }}px;
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5277,6 +5277,11 @@ terser@^5.7.0:
source-map "~0.7.2"
source-map-support "~0.5.19"

three@^0.137.5:
version "0.137.5"
resolved "https://registry.yarnpkg.com/three/-/three-0.137.5.tgz#a1e34bedd0412f2d8797112973dfadac78022ce6"
integrity sha512-rTyr+HDFxjnN8+N/guZjDgfVxgHptZQpf6xfL/Mo7a5JYIFwK6tAq3bzxYYB4Ae0RosDZlDuP+X5aXDXz+XnHQ==

thunky@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
Expand Down

0 comments on commit fbfe90c

Please sign in to comment.